Among the different mobile platforms available, in this post, we will talk about Android and iOS. Both of these platforms provide their specific implementations and tools to validate their app’s integrity, which means to verify that your servers are interacting with the original and unmodified version of the apps. In this post, you’ll get an overview of the methods used by these two platforms to establish the integrity of their apps. These methods are:

  1. For Android - Google SafetyNet
  2. For iOS v13 and below - Device Check
  3. For iOS v14 and above - App Attestation

Let’s start with Android.

Android - SafetyNet Attestation API

The SafetyNet Attestation API is an anti-abuse API that allows app developers to assess the Android device their app is running on. The API should be used as a part of your abuse detection system to help determine whether your servers are interacting with your genuine app running on a genuine Android device.

How it works?

SafetyNet Attestation Flow
SafetyNet Attestation Flow

  1. Android app interacts with Google SafetyNet Attestation API to construct an attestation token formatted in JSON Web Signature (JWS).
  2. The SafetyNet Attestation service evaluates the runtime environment and requests a signed attestation of the assessment results from Google’s servers.
  3. Google’s servers send the signed attestation to the SafetyNet Attestation service on the device.
  4. The SafetyNet Attestation service returns this signed attestation to your app.
  5. This JWS is then sent to a backend service for validation.
  6. The backend service returns the validation results. And to validate the token, the backend service decodes/parses the JWS received from the client app. It should look like this:
{
  "timestampMs": 9860437986543,
   "nonce": "R2Rra24fVm5xa2Mg",
  "apkPackageName": "com.package.name.of.requesting.app",
  "apkCertificateDigestSha256": ["base64 encoded, SHA-256 hash of the
                                  certificate used to sign requesting app"],
  "ctsProfileMatch": true,
  "basicIntegrity": true,
  "evaluationType": "BASIC",
}

For a legit Android app running on a legit Android device basicIntegrity and ctsProfileMatch should be true, nounce should not be empty, and the value of these apkPackageName and apkCertificateDigestSha256 should match with that of your Android app.

iOS v13 and below - Device Check

The DeviceCheck services consist of both a framework interface that you access from your mobile app and an Apple server interface that you access from your backend service.

Someone who modifies your app and distributes it outside the App Store can add unauthorized features like game cheats, ad removal, or access to premium content. The App attest service gives your app a way to assert its validity so that your server can more confidently provide access to sensitive resources.

How it works?

DeviceCheck Flow
DeviceCheck Flow

  1. iOS app interacts with the App attest service to construct an attestation token base64 encoded.
  2. The app attest service returns an attestation token to iOS app.
  3. This token is then sent to a backend service for validation.
  4. The backend service doesn’t unpack/validate the token and instead forwards this to the Apple Device Check server for validation.
  5. If the Apple Device Check server returns 200, then it means the Device Check passed. Otherwise, validation failed.
  6. At this step backend service can device how to proceeed depending on the results received from Apple Server.

A sample request from a backend service to the Apple Device Check server for device validation looks like this.

curl -i --verbose -H "Authorization: Bearer <GeneratedJWT>" \ -X POST --data-binary @ValidValidateDeviceToken Request.json \ https://api.development.devicecheck.apple.com/v1/validate_device_token 

Here, the GeneratedJWT token is generated using the Team Id, Key Id, and the Private Key of your legit iOS app. On how to create this auth token see this: https://help.apple.com/developer-account/#/deva05921840

And Request.json contains the device token for validation along with some other information.

{
   "device_token" : "wlkCDA2Hy/CfMqVAShslBAR/0sAiuRIUm5jQg0aJJ2gZl1cs...",
   "transaction_id" : "5b737ca6-a4c7-488e-b928-8452960c4be9",
   "timestamp" : 1487716472000
} 

iOS v14 and above - App Attestation

For iOS v14 and above the implementation is a multi-step back and forth communication between an iOS app, a backend service, and the Apple App attest service to construct and validate the attestation token.

Every time your app needs to communicate attestation data to your server, the app first asks the server for a unique, one-time challenge. App Attest integrates this challenge into the objects that it provides, and that your app sends back to your server for validation. This makes it harder for an attacker to implement a replay attack.

How it works?

App Attestation Flow
App Attestation Flow

  1. The iOS app interacts with Apple App attest service to get a key identifier.
  2. The iOS app then asks its backend service for the first challenge string.
  3. The backend service generates the first challenge, saves it in a database record, and returns to the app.
  4. The iOS app uses this challenge and the key identifier received in step 1 to fetch an attestation token from App attest service.
  5. The iOS app then sends this attestation token and key identifier to the backend service for validation.
  6. The backend service decodes/parses the attestation token to validate. It should look like this:
{
 "fmt": "apple-appattest",
 "attStmt": {
    "x5c": [
      ["<Buffer 30 82 02 cc ... >"],
      ["<Buffer 30 82 02 36 ... >"]
    ],
    "receipt": ["<Buffer 30 80 06 09 ... >"]
  },
  "authData": ["<Buffer 21 c9 9e 00 ... >"]
}
  1. If validation passes on the backend, then the service saves the CredCert (first buffer in the decoded “x5c” arrays) value and another newly generated second challenge string into the database record.
  2. The backend service sends this second challenge back to the iOS app.
  3. The iOS app uses this second challenge to generate an assertion token from App attest service corresponding to a request body.
  4. The iOS app then sends this assertion token in the successive API requests to the backend for validation.
  5. The backend service decodes/parses the assertion token to validate. It should look this:
{
  "signature": ["<Buffer 30 45 02 20 ... >"],
  "authenticatorData": ["<Buffer 21 c9 9e 00 ... >"]
}
  1. The backend service validates this assertion token before proceeding to execute the API call.

Resources

Discuss on Twitter

Related articles: