> For the complete documentation index, see [llms.txt](https://sentinel.rexiox.co/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://sentinel.rexiox.co/attestation/attest-providers.md).

# Attest Providers

Attestation providers handle platform-specific device integrity verification. On Android, the [**Play Integrity**](https://developer.android.com/google/play/integrity) is used, while on Huawei devices, the [**SysIntegrity**](https://developer.huawei.com/consumer/en/doc/Security-Guides/dysintegritydevelopment-0000001050156331) provides device integrity verification. On iOS, [**App Attest**](https://developer.apple.com/documentation/devicecheck/establishing-your-app-s-integrity) provides secure hardware-backed attestation.

{% hint style="danger" %}
Please configure and implement the providers listed below in strict accordance with their official platform documentation.
{% endhint %}

#### Play Integrity (Android)

On Android, the Play Integrity API is used to verify app and device integrity. It generates an attestation token tied to a secure nonce provided by the backend.

```kotlin
class PlayIntegrityProvider(context: Context) : AttestProvider {

    private val integrityManager = IntegrityManagerFactory.create(context)

    override suspend fun getToken(nonce: String): String {
        val integrityTokenResponse: Task<IntegrityTokenResponse> =
            integrityManager.requestIntegrityToken(
                IntegrityTokenRequest.builder()
                    .setNonce(nonce)
                    .build()
            )

        return suspendCancellableCoroutine { cont ->
            integrityTokenResponse
                .addOnSuccessListener {
                    cont.resume(it.token())
                }
                .addOnFailureListener { e ->
                    cont.resumeWithException(
                        AttestationException(
                            message = "Attestation failure: ${e.localizedMessage}",
                            code = AttestationErrorCode.ATTESTATION_FAILED
                        )
                    )
                }
        }
    }
}
```

#### App Attest (iOS)

On iOS, Apple’s App Attest service is used to generate and validate a device-bound cryptographic attestation. It ensures the app instance is legitimate and unmodified.

```kotlin
class AppAttestProvider : AttestProvider {

    @OptIn(ExperimentalForeignApi::class)
    override suspend fun getToken(nonce: String): String = suspendCancellableCoroutine { cont ->
        val service = DCAppAttestService.sharedService

        if (!service.supported) {
            cont.resumeWithException(
                AttestationException(
                    message = "App Attest is not supported.",
                    code = AttestationErrorCode.ATTESTATION_FAILED
                )
            )
            return@suspendCancellableCoroutine
        }
        
        // Cache keyId for subsequent operations.
        service.generateKeyWithCompletionHandler { keyId, error ->
            if (error != null || keyId == null) {
                cont.resumeWithException(
                    AttestationException(
                        message = "Key could not be generated: ${error?.localizedDescription}",
                        code = AttestationErrorCode.ATTESTATION_FAILED
                    )
                )
                return@generateKeyWithCompletionHandler
            }

            val hash = nonce.encodeToByteArray().sha256()

            service.attestKey(keyId = keyId, clientDataHash = hash) { attestation, attestError ->
                when {
                    attestError != null -> cont.resumeWithException(
                        AttestationException(
                            message = "Attestation failure: ${attestError.localizedDescription}",
                            code = AttestationErrorCode.ATTESTATION_FAILED
                        )
                    )

                    attestation == null -> cont.resumeWithException(
                        AttestationException(
                            message = "Attestation value empty.",
                            code = AttestationErrorCode.ATTESTATION_FAILED
                        )
                    )

                    else -> cont.resume(attestation.base64Encoding())
                }
            }
        }
    }

    @OptIn(ExperimentalForeignApi::class)
    private fun ByteArray.sha256(): NSData = memScoped {
        val input  = allocArray<ByteVar>(size)
        val digest = allocArray<UByteVar>(CC_SHA256_DIGEST_LENGTH)
        forEachIndexed { i, b -> input[i] = b }
        CC_SHA256(input, size.toUInt(), digest)
        NSData.dataWithBytes(digest, CC_SHA256_DIGEST_LENGTH.toULong())
    }

    @OptIn(ExperimentalForeignApi::class)
    fun ByteArray.toNSData(): NSData = memScoped {
        val buf = allocArray<ByteVar>(size)
        forEachIndexed { i, b -> buf[i] = b }
        NSData.dataWithBytes(buf, size.toULong())
    }
}
```

#### SysIntegrity (Huawei)

```kotlin
class SysIntegrityProvider(
    private val context: Context,
    private val appId: String,
) : AttestProvider {

    override suspend fun getToken(nonce: String): String {
        val nonceBytes = nonce.toByteArray(StandardCharsets.UTF_8)

        val sysIntegrityRequest = SysIntegrityRequest().apply {
            this.appId = this@SysIntegrityProvider.appId
            this.nonce = nonceBytes
            this.alg = "PS256"
        }

        return suspendCancellableCoroutine { continuation ->
            SafetyDetect.getClient(context)
                .sysIntegrity(sysIntegrityRequest)
                .addOnSuccessListener { response ->
                    val jwsStr = response.result

                    if (jwsStr.isNotBlank()) {
                        continuation.resume(jwsStr)
                    } else {
                        continuation.resumeWithException(
                            AttestationException(
                                message = "Attestation failure: SysIntegrity response result is null or empty",
                                code = AttestationErrorCode.ATTESTATION_FAILED
                            )
                        )
                    }
                }
                .addOnFailureListener { e ->
                    continuation.resumeWithException(
                        AttestationException(
                            message = "Attestation failure: ${e.localizedMessage}",
                            code = AttestationErrorCode.ATTESTATION_FAILED
                        )
                    )
                }
        }
    }
}
```
