> For the complete documentation index, see [llms.txt](https://docs.blerify.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.blerify.com/introduction-to-verification/learn/platform-attestation.md).

# Platform Attestation

Android and iOS expose fundamentally different attestation capabilities to third-party wallet apps. This page explains what each platform can and cannot prove, how that asymmetry shapes the evidence Blerify returns to your verification endpoint, and how to write a policy that accounts for it.

***

## Prerequisites

You should be familiar with Blerify's assurance tiers (Basic, Standard, Premium) and the general shape of the verification evidence response. If you haven't read [Assurance Tiers](/introduction-to-verification/learn/assurance-tiers.md) yet, start there.

***

## Why the platforms differ

At Standard and Premium tiers, Blerify asks the wallet to submit attestation data alongside the credential presentation. That data supports two claims: the signing key lives in secure hardware, and the user authenticated with their biometric before every signing operation.

On Android, a single API — Android Key Attestation — lets the wallet produce an X.509 certificate chain signed up to a Google-managed root. That chain encodes the key's properties directly: where it lives (TEE or StrongBox), whether biometric is required to use it, and whether the key is destroyed when a new biometric is enrolled. Every property is hardware-signed and cannot be forged by software, including on a rooted device.

On iOS, the equivalent API is Apple App Attest. It proves that an unmodified copy of the registered app, running on genuine Apple hardware, possesses the signing key. What it cannot prove is where inside the device the key lives, whether biometric is required for each signature, or whether the key will be invalidated on enrollment change. Apple provides no API that attests Secure Enclave key properties to third-party apps — the wallet reports those properties itself.

This is an architectural decision by Apple, not a limitation of any wallet implementation. The gap has been present since App Attest launched in 2021 and affects every third-party wallet on iOS.

***

## What this means in the evidence response

The asymmetry surfaces in two places: fields whose values differ by platform, and `_reason` fields that explain why a field is absent.

### `security_level_attested`

On Android, `key_attestation.security_level` — either `STRONG_BOX` or `TRUSTED_ENVIRONMENT` — is extracted directly from the hardware-signed certificate chain. Blerify sets `security_level_attested: true`.

On iOS, the wallet self-reports `SECURE_ENCLAVE`. Blerify sets `security_level_attested: false`.

```json
// Android — Standard tier
"key_attestation": {
  "security_level": "STRONG_BOX",
  "security_level_attested": true,
  "platform": "android",
  "format": "android_key_attestation",
  "key_biometric_binding": {
    "biometric_required": true,
    "auth_type": ["FINGERPRINT", "FACE"],
    "auth_timeout_seconds": 0,
    "invalidated_on_enrollment_change": true,
    "attested": true
  }
}
```

```json
// iOS — Standard tier
"key_attestation": {
  "security_level": "SECURE_ENCLAVE",
  "security_level_attested": false,
  "platform": "ios",
  "format": "app_attest",
  "key_biometric_binding": {
    "biometric_required": true,
    "auth_type": ["FACE"],
    "auth_timeout_seconds": 0,
    "invalidated_on_enrollment_change": true,
    "attested": false
  }
}
```

`attested: false` on iOS means the `key_biometric_binding` properties are self-reported by the wallet. On a genuine device running an unmodified app, they accurately reflect how the Secure Enclave key was configured. On a jailbroken device, they can be forged. Android Key Attestation cannot be forged because the certificate chain is signed by hardware that the operating system cannot reach.

### `key_biometric_binding` fields

These five fields describe the access-control policy that governs the signing key.

| Field                              | What it means                                                                                                                      | Android           | iOS            |
| ---------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------- | ----------------- | -------------- |
| `biometric_required`               | Biometric authentication is required before every signing operation                                                                | Hardware-attested | Self-reported  |
| `auth_type`                        | Which authenticator the key accepts: `FINGERPRINT`, `FACE`, or `DEVICE_CREDENTIAL`                                                 | Hardware-attested | Self-reported  |
| `auth_timeout_seconds`             | Seconds of reuse allowed after authentication. `0` means every signing operation requires fresh authentication — no caching window | Hardware-attested | Self-reported  |
| `invalidated_on_enrollment_change` | The key is destroyed if any new biometric is added to the device                                                                   | Hardware-attested | Self-reported  |
| `attested`                         | Whether the four fields above are hardware-attested (`true`) or self-reported (`false`)                                            | Always `true`     | Always `false` |

`auth_timeout_seconds: 0` combined with `invalidated_on_enrollment_change: true` is the strongest configuration. The key cannot be used without the holder's biometric at the moment of signing, and enrolling any new biometric on the device permanently destroys the key.

### Strong biometric vs. device credential

Both platforms support two key-binding paths: the **strong biometric path** (fingerprint or face recognition) and the **device credential path** (PIN, pattern, or passcode). The path chosen at key-creation time determines what `auth_type` reports.

On the strong biometric path, `auth_type` is `["FINGERPRINT"]`, `["FACE"]`, or both, and `invalidated_on_enrollment_change` is `true`. On the device credential path, `auth_type` is `["DEVICE_CREDENTIAL"]` and `invalidated_on_enrollment_change` is `false` — a PIN change does not destroy the key. This distinction matters for non-repudiation claims: a key on the device credential path cannot be made inaccessible to an attacker who knows the PIN.

### `device_attestation` at Premium on iOS

At Premium, an Android wallet submits a Play Integrity token that produces a device-integrity verdict: locked bootloader, certified OS image, legitimate app install source, and recent security patches. These are independent signals that key attestation does not cover.

iOS has no equivalent API for third-party apps. App Attest, which is already fully consumed at Standard tier, verifies app identity against Apple's servers but does not produce a device-integrity verdict, cannot detect jailbreaks, and does not inspect the OS image.

At Premium on iOS, `device_attestation` is always `null` with `device_attestation_reason: "platform_not_available"`.

```json
// iOS — Premium tier
"device_attestation": null,
"device_attestation_reason": "platform_not_available"
```

This is not a failure — it is a structural platform constraint. The practical consequence is that the Premium upgrade on iOS comes entirely from the biometric layer. `liveness_verification` and `coercion_check` work identically on both platforms.

### `device_metadata` attestation

Android Key Attestation embeds the device model and OS version in the hardware-signed certificate chain, so `device_metadata.attested` is `true` on Android. App Attest proves device genuineness but does not include model or OS version in the attestation object, so those values are self-reported by the wallet on iOS and `device_metadata.attested` is `false`.

```json
// Android
"device_metadata": {
  "device_model": "Pixel 8",
  "os_version": "Android 15 (patch 2026-03-05)",
  "attested": true,
  "attestation_source": "android_key_attestation"
}

// iOS
"device_metadata": {
  "device_model": "iPhone 15 Pro",
  "os_version": "iOS 18.3",
  "attested": false,
  "attestation_source": "wallet_self_reported"
}
```

***

## The `_reason` field

Every optional evidence field has a companion `_reason` field. When the main field is populated, `_reason` is `null`. When the main field is `null`, `_reason` explains why.

| Value                       | Meaning                                                                                                | What to do                                                                                              |
| --------------------------- | ------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------- |
| `null`                      | The field is populated                                                                                 | N/A                                                                                                     |
| `platform_not_available`    | The platform has no API for this capability — for example, iOS `device_attestation` at Premium         | Structural limit — accept, or apply a platform-specific policy rule                                     |
| `wallet_not_supported`      | The wallet does not implement Blerify extensions                                                       | Capability gap — downgrade assurance or direct users to a compatible wallet                             |
| `not_requested`             | Your verification configuration did not request this tier or feature                                   | Expected absence                                                                                        |
| `user_declined`             | The user denied a required permission (camera access, biometric prompt)                                | Active refusal — route to manual review rather than treating identically to a structural platform limit |
| `failed`                    | Validation was attempted but the data was rejected — invalid token, spoof detected, assertion mismatch | Treat as a strong integrity signal — investigate                                                        |
| `attestation_not_supported` | Older Android device where the key is in hardware but cryptographic attestation is unavailable         | Hardware-backed but unprovable — accept or downgrade per your policy                                    |

The `_reason` field is always present in the response schema — `null` when the main field is populated. You can rely on a consistent shape and never need to infer absence semantics from a missing field.

The distinction between `platform_not_available` and `user_declined` matters for policy. A device that structurally cannot produce a signal is different from a user who actively chose not to provide one. Consider routing `user_declined` to a separate review queue rather than treating it the same as a platform limit.

Similarly, `attestation_not_supported` on an older Android device is different from `failed`. The former is a legacy hardware limitation affecting less than 1% of the active Android fleet; the latter is an active integrity failure.

***

## Platform comparison

| Evidence field                            | Android                                                            | iOS                                                   |
| ----------------------------------------- | ------------------------------------------------------------------ | ----------------------------------------------------- |
| `key_attestation.security_level`          | Hardware-attested (`STRONG_BOX` or `TRUSTED_ENVIRONMENT`)          | Self-reported (`SECURE_ENCLAVE`)                      |
| `key_attestation.security_level_attested` | `true`                                                             | `false`                                               |
| `key_biometric_binding.*`                 | All five fields hardware-attested                                  | All five fields self-reported                         |
| `key_biometric_binding.attested`          | `true`                                                             | `false` (always)                                      |
| `device_metadata.attested`                | `true`                                                             | `false`                                               |
| `device_metadata.attestation_source`      | `android_key_attestation`                                          | `wallet_self_reported`                                |
| `device_attestation` at Premium           | Play Integrity — device integrity, OS certification, app integrity | `null`, `_reason: "platform_not_available"`           |
| `liveness_verification` at Premium        | Full liveness — platform-neutral                                   | Full liveness — platform-neutral                      |
| `coercion_check` at Premium               | Facial analysis on liveness frames — platform-neutral              | Facial analysis on liveness frames — platform-neutral |

***

## Key invalidation on enrollment change

### Android

When a new biometric is enrolled after the signing key was created — for example, if someone adds their own fingerprint to a stolen phone — the key is destroyed automatically on the strong biometric path. The `invalidated_on_enrollment_change: true` field tells you this protection is active, and `attested: true` tells you it is hardware-enforced.

On the device credential path (`auth_type: ["DEVICE_CREDENTIAL"]`), the key is PIN-gated and a PIN change does not destroy it. Check `auth_type` to understand which path is in use before making non-repudiation claims.

### iOS

The equivalent protection comes from an access-control flag set on the Secure Enclave key at creation time. A key created with this flag becomes inaccessible when any new biometric is enrolled — the Secure Enclave enforces this locally. The wallet self-reports this via `invalidated_on_enrollment_change: true`, and `attested: false` tells you it is locally enforced rather than remotely verifiable.

On the device credential path, enrollment changes do not invalidate the key, matching Android's device credential behavior.

***

## TEE vs. StrongBox on Android

Android Key Attestation distinguishes two hardware security levels.

**TEE (Trusted Execution Environment)** is a hardware-isolated execution environment present on virtually all Android devices since Android 7.0. It runs in a secure partition of the same processor, enforcing memory separation between the normal Android OS and the secure world where keys are managed. TEE has a broader attack surface than discrete secure hardware — known CVEs exist for specific chipset implementations, and exploitation typically requires an attacker to first gain code execution on the device.

**StrongBox** is a dedicated, tamper-resistant secure processor with its own CPU, storage, and random number generator. It is not present on all devices — it is common on modern flagship phones but absent from many mid-range and budget devices. StrongBox is significantly harder to attack than TEE because it is physically separate hardware.

Both are accepted at Standard and Premium tiers. Blerify reports `security_level` so you can apply your own acceptance policy. Most regulated identity verification use cases accept TEE — exploiting it to bypass biometric-gated key signing requires chaining multiple chipset-specific vulnerabilities. If your requirements call for StrongBox-only, evaluate `security_level` in the evidence and enforce your policy accordingly.

***

## Older Android devices and `attestation_not_supported`

A small fraction of Android devices — less than 1% of the active fleet — launched before Android 8.0 with hardware security modules that predate Key Attestation. On these devices, the signing key is still in hardware and the biometric gates still work, but Blerify cannot produce the X.509 certificate chain that proves hardware residency.

In this case, `key_attestation_reason` is `"attestation_not_supported"`. Whether to accept a hardware-backed but unprovable key is a policy decision. Most verifiers treat it the same as a proven hardware-backed key given the low prevalence and the fact that the user still authenticated with their biometric to authorize the presentation.

***

## Writing a policy that accounts for iOS

**Accept iOS without device attestation.** Most regulated use cases accept that iOS does not expose a Play Integrity equivalent for third-party apps. The combination of App Attest at Standard — proving genuine Apple hardware and an unmodified app — plus liveness at Premium covers the majority of identity verification and account-opening flows. Document the acceptance explicitly in your risk assessment.

**Differentiate `security_level_attested: false` by platform.** On iOS, this value always reflects a platform constraint, not a degraded or suspicious result. Pair it with a check on the `platform` field to confirm you are seeing the expected iOS self-reported case rather than an anomaly.

**Treat `attested: false` differently from `failed`.** An iOS key with `attested: false` and a valid App Attest assertion is meaningfully different from a key whose attestation failed validation. The former is a known platform limitation; the latter is an active integrity failure worth investigating. Your policy logic should handle them separately.

**Use `user_declined` as an escalation signal.** A user who denies the biometric prompt or camera permission has actively interrupted the flow. This is a different risk category from an iOS device that structurally cannot produce device attestation. Route `user_declined` to a review queue rather than treating it as equivalent to a structural absence.

**Require liveness for high-value iOS flows.** Since device attestation is unavailable on iOS, `liveness_verification` at Premium is the primary compensating control for transactions that warrant it. If you need the highest available assurance on iOS, make liveness a hard requirement in your verification configuration.

**Do not block on `"platform_not_available"`.** Blocking iOS users because the platform cannot produce a Play Integrity equivalent would exclude a large share of legitimate users without a corresponding security benefit. Note the limitation, compensate with liveness where the risk warrants it, and document the accepted residual gap.

***

## What the layered iOS evidence adds up to

At Premium on iOS, the combination of evidence across all layers supports a defensible assurance level even without hardware-attested `key_biometric_binding` and without device attestation.

1. **App Attest** proves the request came from a genuine Apple device running an unmodified instance of the registered app. A modified or unauthorized app fails App Attest before any signing occurs.
2. **Secure Enclave key** is configured by the wallet with enrollment-change invalidation. The key is tied to the biometric enrollment set at key-creation time and becomes inaccessible if that set changes. The Secure Enclave enforces this locally — Apple does not provide a remote attestation of it, but the wallet has no mechanism to bypass it on a genuine, unmodified device.
3. **Liveness verification** confirms that the person physically present is the credential holder by comparing a live facial capture against the portrait embedded in the credential presentation.

The gap versus Android is that the Secure Enclave configuration is enforced by Apple's hardware rather than proven by a verifiable certificate chain. For most regulated use cases, that is an acceptable residual risk.

***

## Non-repudiation and the biometric binding chain

At Standard tier, the combination of key attestation and biometric binding supports a non-repudiation argument: the credential was presented by someone who held the device, authenticated with the enrolled biometric, and whose signing key would have been destroyed had anyone modified the biometric enrollment set.

The argument is strongest on Android, where each property in `key_biometric_binding` is hardware-attested. On iOS, it relies on Apple's local enforcement of the Secure Enclave configuration rather than on a verifiable proof. In most jurisdictions this is sufficient to shift the burden of proof to the credential holder to demonstrate they did not authorize the presentation.

At Premium on iOS, `liveness_verification` strengthens the chain by confirming that the person who authorized the presentation was physically present and matched the credential's portrait.

Whether this evidence constitutes legally sufficient non-repudiation depends on the jurisdiction and applicable regulatory framework. Your legal team should evaluate the evidence chain against your specific requirements.

***

## Next steps

**Continue to** [**Assurance Tiers**](/introduction-to-verification/learn/assurance-tiers.md) — how Basic, Standard, and Premium differ, what each proves per platform, and how to choose the right tier for your use case.

See also: [Biometric Binding](/introduction-to-verification/learn/biometric-binding.md) · [Read a Verification Result](/introduction-to-verification/build/read-a-verification-result.md) · [API Reference](https://dev.blerify.com)


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.blerify.com/introduction-to-verification/learn/platform-attestation.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
