Esito dell'integrità

Questa pagina descrive come interpretare e utilizzare il verdetto di integrità restituito. A prescindere dal fatto che tu effettui una richiesta API standard o classica, il verdetto di integrità viene restituito nello stesso formato con contenuti simili. Il verdetto di integrità comunica informazioni sulla validità di dispositivi, app e account. Il server della tua app può utilizzare il payload risultante in un esito decriptato e verificato per determinare come procedere al meglio con una determinata azione o richiesta nella tua app.

Formato dell'esito relativo all'integrità restituito

Il payload è in formato JSON in testo normale e contiene indicatori di integrità insieme alle informazioni fornite dallo sviluppatore.

La struttura generale del payload è la seguente:

{
  requestDetails: { ... }
  appIntegrity: { ... }
  deviceIntegrity: { ... }
  accountDetails: { ... }
  environmentDetails: { ... }
}

Prima di controllare ogni esito relativo all'integrità, devi verificare che i valori nel campo requestDetails corrispondano a quelli della richiesta originale. Le seguenti sezioni descrivono in modo più dettagliato ogni campo.

Campo dei dettagli della richiesta

Il campo requestDetails contiene informazioni sulla richiesta, incluse le informazioni fornite dallo sviluppatore nella requestHash per le richieste standard e nel nonce per le richieste classiche.

Per le richieste API standard:

requestDetails: {
  // Application package name this attestation was requested for.
  // Note that this field might be spoofed in the middle of the request.
  requestPackageName: "com.package.name"
  // Request hash provided by the developer.
  requestHash: "aGVsbG8gd29scmQgdGhlcmU"
  // The timestamp in milliseconds when the integrity token
  // was prepared (computed on the server).
  timestampMillis: "1675655009345"
}

Questi valori devono corrispondere a quelli della richiesta originale. Verifica pertanto la parte requestDetails del payload JSON assicurandoti che requestPackageName e requestHash corrispondano a quanto inviato nella richiesta originale, come mostrato nel seguente snippet di codice:

Kotlin

val requestDetails = JSONObject(payload).getJSONObject("requestDetails")
val requestPackageName = requestDetails.getString("requestPackageName")
val requestHash = requestDetails.getString("requestHash")
val timestampMillis = requestDetails.getLong("timestampMillis")
val currentTimestampMillis = ...

// Ensure the token is from your app.
if (!requestPackageName.equals(expectedPackageName)
        // Ensure the token is for this specific request
    || !requestHash.equals(expectedRequestHash)
        // Ensure the freshness of the token.
    || currentTimestampMillis - timestampMillis > ALLOWED_WINDOW_MILLIS) {
        // The token is invalid! See below for further checks.
        ...
}

Java

RequestDetails requestDetails =
    decodeIntegrityTokenResponse
    .getTokenPayloadExternal()
    .getRequestDetails();
String requestPackageName = requestDetails.getRequestPackageName();
String requestHash = requestDetails.getRequestHash();
long timestampMillis = requestDetails.getTimestampMillis();
long currentTimestampMillis = ...;

// Ensure the token is from your app.
if (!requestPackageName.equals(expectedPackageName)
        // Ensure the token is for this specific request.
    || !requestHash.equals(expectedRequestHash)
        // Ensure the freshness of the token.
    || currentTimestampMillis - timestampMillis > ALLOWED_WINDOW_MILLIS) {
        // The token is invalid! See below for further checks.
        ...
}

Per le richieste API classiche:

requestDetails: {
  // Application package name this attestation was requested for.
  // Note that this field might be spoofed in the middle of the
  // request.
  requestPackageName: "com.package.name"
  // base64-encoded URL-safe no-wrap nonce provided by the developer.
  nonce: "aGVsbG8gd29scmQgdGhlcmU"
  // The timestamp in milliseconds when the request was made
  // (computed on the server).
  timestampMillis: "1617893780"
}

Questi valori devono corrispondere a quelli della richiesta originale. Verifica pertanto la parte requestDetails del payload JSON assicurandoti che requestPackageName e nonce corrispondano a quanto inviato nella richiesta originale, come mostrato nel seguente snippet di codice:

Kotlin

val requestDetails = JSONObject(payload).getJSONObject("requestDetails")
val requestPackageName = requestDetails.getString("requestPackageName")
val nonce = requestDetails.getString("nonce")
val timestampMillis = requestDetails.getLong("timestampMillis")
val currentTimestampMillis = ...

// Ensure the token is from your app.
if (!requestPackageName.equals(expectedPackageName)
        // Ensure the token is for this specific request. See 'Generate a nonce'
        // section of the doc on how to store/compute the expected nonce.
    || !nonce.equals(expectedNonce)
        // Ensure the freshness of the token.
    || currentTimestampMillis - timestampMillis > ALLOWED_WINDOW_MILLIS) {
        // The token is invalid! See below for further checks.
        ...
}

Java

JSONObject requestDetails =
    new JSONObject(payload).getJSONObject("requestDetails");
String requestPackageName = requestDetails.getString("requestPackageName");
String nonce = requestDetails.getString("nonce");
long timestampMillis = requestDetails.getLong("timestampMillis");
long currentTimestampMillis = ...;

// Ensure the token is from your app.
if (!requestPackageName.equals(expectedPackageName)
        // Ensure the token is for this specific request. See 'Generate a nonce'
        // section of the doc on how to store/compute the expected nonce.
    || !nonce.equals(expectedNonce)
        // Ensure the freshness of the token.
    || currentTimestampMillis - timestampMillis > ALLOWED_WINDOW_MILLIS) {
        // The token is invalid! See below for further checks.
        ...
}

Campo Integrità dell'applicazione

Il campo appIntegrity contiene informazioni relative al pacchetto.

appIntegrity: {
  // PLAY_RECOGNIZED, UNRECOGNIZED_VERSION, or UNEVALUATED.
  appRecognitionVerdict: "PLAY_RECOGNIZED"
  // The package name of the app.
  // This field is populated iff appRecognitionVerdict != UNEVALUATED.
  packageName: "com.package.name"
  // The sha256 digest of app certificates (base64-encoded URL-safe).
  // This field is populated iff appRecognitionVerdict != UNEVALUATED.
  certificateSha256Digest: ["6a6a1474b5cbbb2b1aa57e0bc3"]
  // The version of the app.
  // This field is populated iff appRecognitionVerdict != UNEVALUATED.
  versionCode: "42"
}

appRecognitionVerdict può avere i seguenti valori:

PLAY_RECOGNIZED
L'app e il certificato corrispondono alle versioni distribuite da Google Play.
UNRECOGNIZED_VERSION
Il nome del certificato o del pacchetto non corrisponde ai record di Google Play.
UNEVALUATED
L'integrità dell'applicazione non è stata valutata. Un requisito necessario non è stato soddisfatto, ad esempio perché il dispositivo non è abbastanza affidabile.

Per assicurarti che il token sia stato generato da un'app creata da te, verifica che l'integrità dell'applicazione sia quella prevista, come mostrato nel seguente snippet di codice:

Kotlin

val appIntegrity = JSONObject(payload).getJSONObject("appIntegrity")
val appRecognitionVerdict = appIntegrity.getString("appRecognitionVerdict")

if (appRecognitionVerdict == "PLAY_RECOGNIZED") {
    // Looks good!
}

Java

JSONObject appIntegrity =
    new JSONObject(payload).getJSONObject("appIntegrity");
String appRecognitionVerdict =
    appIntegrity.getString("appRecognitionVerdict");

if (appRecognitionVerdict.equals("PLAY_RECOGNIZED")) {
    // Looks good!
}

Puoi anche controllare manualmente il nome del pacchetto e la versione dell'app e i certificati dell'app.

Campo Integrità del dispositivo

Il campo deviceIntegrity può contenere un singolo valore, deviceRecognitionVerdict, con una o più etichette che rappresentano l'efficacia con cui un dispositivo può applicare l'integrità dell'app. Se un dispositivo non soddisfa i criteri di nessuna etichetta, il campo deviceIntegrity è vuoto.

deviceIntegrity: {
  // "MEETS_DEVICE_INTEGRITY" is one of several possible values.
  deviceRecognitionVerdict: ["MEETS_DEVICE_INTEGRITY"]
}

Per impostazione predefinita, deviceRecognitionVerdict può contenere i seguenti elementi:

MEETS_DEVICE_INTEGRITY
L'app viene eseguita su un dispositivo Android con Google Play Services. Il dispositivo supera i controlli relativi all'integrità del sistema e soddisfa i requisiti di compatibilità di Android.
Vuoto (valore vuoto)
L'app è in esecuzione su un dispositivo che presenta segnali di attacco (ad esempio l'hook delle API) o una compromissione del sistema (ad esempio il rooting) oppure l'app non è in esecuzione su un dispositivo fisico (ad esempio un emulatore che non supera i controlli di integrità di Google Play).

Per assicurarti che il token provenga da un dispositivo attendibile, verifica che deviceRecognitionVerdict sia come previsto, come mostrato nel seguente snippet di codice:

Kotlin

val deviceIntegrity =
    JSONObject(payload).getJSONObject("deviceIntegrity")
val deviceRecognitionVerdict =
    if (deviceIntegrity.has("deviceRecognitionVerdict")) {
        deviceIntegrity.getJSONArray("deviceRecognitionVerdict").toString()
    } else {
        ""
    }

if (deviceRecognitionVerdict.contains("MEETS_DEVICE_INTEGRITY")) {
    // Looks good!
}

Java

JSONObject deviceIntegrity =
    new JSONObject(payload).getJSONObject("deviceIntegrity");
String deviceRecognitionVerdict =
    deviceIntegrity.has("deviceRecognitionVerdict")
    ? deviceIntegrity.getJSONArray("deviceRecognitionVerdict").toString()
    : "";

if (deviceRecognitionVerdict.contains("MEETS_DEVICE_INTEGRITY")) {
    // Looks good!
}

Se hai problemi con il dispositivo di test che soddisfa l'integrità del dispositivo, assicurati che la ROM di fabbrica sia installata (ad esempio reimpostando il dispositivo) e che il bootloader sia bloccato. Puoi anche creare test dell'API Play Integrity in Play Console.

Etichette condizionali dei dispositivi

Se la tua app sta per essere rilasciata su Google Play Giochi per PC, il deviceRecognitionVerdict può contenere anche la seguente etichetta:

MEETS_VIRTUAL_INTEGRITY
L'app è installata su un emulatore Android con Google Play Services. L'emulatore supera i controlli relativi all'integrità del sistema e soddisfa i requisiti fondamentali di compatibilità con Android.

Informazioni del dispositivo facoltative

Se attivi la ricezione di altre etichette nell'esito relativo all'integrità, deviceRecognitionVerdict può contenere le seguenti etichette aggiuntive:

MEETS_BASIC_INTEGRITY
L'app è in esecuzione su un dispositivo che supera i controlli di base di integrità del sistema. Il dispositivo potrebbe non soddisfare i requisiti di compatibilità di Android e potrebbe non essere approvato per l'esecuzione di Google Play Services. Ad esempio, sul dispositivo potrebbe essere in esecuzione una versione non riconosciuta di Android, avere un bootloader sbloccato o non essere stato certificato dal produttore.
MEETS_STRONG_INTEGRITY
L'app viene eseguita su un dispositivo Android con Google Play Services e ha una solida garanzia di integrità del sistema, ad esempio una prova dell'integrità dell'avvio supportata da hardware. Il dispositivo supera i controlli di integrità del sistema e soddisfa i requisiti di compatibilità di Android.

Un singolo dispositivo restituirà più etichette del dispositivo nel test relativo all'integrità del dispositivo se viene soddisfatto ciascuno dei criteri dell'etichetta.

Attività recente del dispositivo (beta)

Puoi anche attivare l'attività recente del dispositivo, che indica quante volte la tua app ha richiesto un token di integrità su un dispositivo specifico nell'ultima ora. Puoi utilizzare l'attività recente del dispositivo per proteggere la tua app da dispositivi iperattivi inaspettati che potrebbero indicare un attacco attivo. Puoi decidere in che misura considerare attendibile ogni livello di attività recente del dispositivo in base al numero di volte in cui prevedi che l'app venga installata su un dispositivo tipico per richiedere un token di integrità ogni ora.

Se attivi la ricezione di recentDeviceActivity, il campo deviceIntegrity avrà due valori:

deviceIntegrity: {
  deviceRecognitionVerdict: ["MEETS_DEVICE_INTEGRITY"]
  recentDeviceActivity: {
    // "LEVEL_2" is one of several possible values.
    deviceActivityLevel: "LEVEL_2"
  }
}

Le definizioni di deviceActivityLevel variano a seconda della modalità e possono avere uno dei seguenti valori:

Livello di attività recente del dispositivo Richieste standard nell'ultima ora Richieste classiche nell'ultima ora
LEVEL_1 (valore più basso) L'app ha richiesto al massimo 10 token per l'integrità su questo dispositivo. L'app ha richiesto al massimo 5 token per l'integrità su questo dispositivo.
LEVEL_2 L'app ha richiesto tra 11 e 25 token di integrità su questo dispositivo. L'app ha richiesto tra 6 e 15 token di integrità su questo dispositivo.
LEVEL_3 L'app ha richiesto tra 26 e 50 token di integrità su questo dispositivo. L'app ha richiesto tra 16 e 30 token di integrità su questo dispositivo.
LEVEL_4 (valore più alto) L'app ha richiesto più di 50 token per l'integrità su questo dispositivo. L'app ha richiesto più di 30 token per l'integrità su questo dispositivo.
UNEVALUATED L'attività recente del dispositivo non è stata valutata. Il motivo potrebbe essere uno dei seguenti:
  • Il dispositivo non è abbastanza attendibile.
  • La versione dell'app installata sul dispositivo non è nota a Google Play.
  • Problemi tecnici sul dispositivo.

Campo dei dettagli dell'account

Il campo accountDetails contiene un singolo valore, appLicensingVerdict, che rappresenta lo stato di licenza dell'app su Google Play per l'account utente a cui è stato eseguito l'accesso sul dispositivo. Se l'account utente dispone della licenza Play per l'app, significa che l'utente l'ha scaricata o acquistata da Google Play.

accountDetails: {
  // This field can be LICENSED, UNLICENSED, or UNEVALUATED.
  appLicensingVerdict: "LICENSED"
}

appLicensingVerdict può avere uno dei seguenti valori:

LICENSED
L'utente dispone di autorizzazione per l'app. In altre parole, l'utente ha installato o acquistato la tua app su Google Play.
UNLICENSED
L'utente non dispone di diritti per l'app. Questo accade, ad esempio, quando l'utente installa l'app tramite sideload o non la acquisisce da Google Play. Per risolvere il problema, puoi mostrare la finestra di dialogo GET_LICENSED agli utenti.
UNEVALUATED

I dettagli relativi alle licenze non sono stati valutati perché mancava un requisito necessario.

Questo potrebbe accadere per diversi motivi, tra cui:

  • Il dispositivo non è abbastanza attendibile.
  • La versione dell'app installata sul dispositivo non è nota a Google Play.
  • L'utente non ha eseguito l'accesso a Google Play.

Per verificare che l'utente disponga di diritti per l'app, verifica che appLicensingVerdict sia come previsto, come mostrato nel seguente snippet di codice:

Kotlin

val accountDetails = JSONObject(payload).getJSONObject("accountDetails")
val appLicensingVerdict = accountDetails.getString("appLicensingVerdict")

if (appLicensingVerdict == "LICENSED") {
    // Looks good!
}

Java

JSONObject accountDetails =
    new JSONObject(payload).getJSONObject("accountDetails");
String appLicensingVerdict = accountDetails.getString("appLicensingVerdict");

if (appLicensingVerdict.equals("LICENSED")) {
    // Looks good!
}

Campo dei dettagli dell'ambiente

Puoi anche attivare indicatori aggiuntivi sull'ambiente. Il rischio di accesso all'app indica alla tua app se sono in esecuzione altre app che potrebbero essere utilizzate per acquisire lo schermo, visualizzare overlay o controllare il dispositivo. Il verdetto Play Protect indica se Google Play Protect è attivo sul dispositivo e se ha trovato malware noto.

Se hai attivato l'esito del rischio di accesso all'app o l'esito Play Protect in Google Play Console, la risposta dell'API includerà il campo environmentDetails. Il campo environmentDetails può contenere due valori: appAccessRiskVerdict e playProtectVerdict.

Esito del rischio di accesso all'app (beta)

Una volta abilitato, il campo environmentDetails nel payload dell'API Play Integrity conterrà il nuovo esito del rischio di accesso all'app.

{
  requestDetails: { ... }
  appIntegrity: { ... }
  deviceIntegrity: { ... }
  accountDetails: { ... }
  environmentDetails: {
      appAccessRiskVerdict: {
          // This field contains one or more responses, for example the following.
          appsDetected: ["KNOWN_INSTALLED", "UNKNOWN_INSTALLED", "UNKNOWN_CAPTURING"]
      }
 }
}

Se è stato valutato il rischio di accesso all'app, appAccessRiskVerdict contiene il campo appsDetected con una o più risposte. Queste risposte rientrano in uno dei seguenti due gruppi, a seconda dell'origine di installazione delle app rilevate:

  • App di sistema o di Google Play: app installate da Google Play o precaricate dal produttore del dispositivo sulla partizione di sistema del dispositivo (identificata con FLAG_SYSTEM). Le risposte per queste app sono precedute dal carattere KNOWN_.

  • Altre app: le app non installate da Google Play. Sono escluse le app precaricate sulla partizione di sistema dal produttore del dispositivo. Le risposte per queste app sono precedute da UNKNOWN_.

Possono essere restituite le seguenti risposte:

KNOWN_INSTALLED, UNKNOWN_INSTALLED
Sono presenti app installate che corrispondono all'origine di installazione corrispondente.
KNOWN_CAPTURING, UNKNOWN_CAPTURING
Esistono app in esecuzione con autorizzazioni abilitate che potrebbero essere usate per visualizzare lo schermo mentre l'app è in esecuzione. Sono esclusi tutti i servizi di accessibilità verificati noti a Google Play in esecuzione sul dispositivo.
KNOWN_CONTROLLING, UNKNOWN_CONTROLLING
Esistono app in esecuzione con autorizzazioni abilitate che potrebbero essere usate per controllare il dispositivo e controllare direttamente gli input nella tua app e che potrebbero essere usate per acquisire input e output della tua app. Sono esclusi eventuali servizi di accessibilità verificati noti a Google Play in esecuzione sul dispositivo.
KNOWN_OVERLAYS, UNKNOWN_OVERLAYS
Sono presenti app in esecuzione per cui sono state abilitate autorizzazioni che possono essere utilizzate per visualizzare overlay nella tua app. Sono esclusi tutti i servizi di accessibilità verificati noti a Google Play in esecuzione sul dispositivo.
EMPTY (valore vuoto)

Il rischio di accesso all'app non viene valutato se manca un requisito necessario. In questo caso, il campo appAccessRiskVerdict è vuoto. Ciò potrebbe accadere per diversi motivi, tra cui:

  • Il dispositivo non è abbastanza attendibile.
  • Il fattore di forma del dispositivo non è uno smartphone, un tablet o un pieghevole.
  • Sul dispositivo non è installato Android 6 (livello API 23) o versioni successive.
  • La versione dell'app installata sul dispositivo non è nota a Google Play.
  • La versione del Google Play Store sul dispositivo è obsoleta.
  • Solo giochi: l'account utente non dispone di una licenza Google Play per il gioco.

Il rischio di accesso all'app esclude automaticamente i servizi di accessibilità verificati che sono stati sottoposti a un controllo dell'accessibilità di Google Play migliorato (installato da qualsiasi store sul dispositivo). "Escluso" significa che i servizi di accessibilità verificati in esecuzione sul dispositivo non restituiscono una risposta di acquisizione, controllo o overlay nell'esito del rischio di accesso all'app. Per richiedere una revisione migliorata dell'accessibilità di Google Play per la tua app di accessibilità, pubblicala su Google Play assicurandoti che il flag isAccessibilityTool sia impostato su true nel file manifest dell'app, oppure richiedi una revisione.

La seguente tabella fornisce alcuni esempi di esiti e il relativo significato (questa tabella non elenca tutti i possibili risultati):

Esempio di risposta all'esito del rischio di accesso all'app Interpretazione
appsDetected:
["KNOWN_INSTALLED"]
Sono presenti solo app installate che sono riconosciute da Google Play o precaricate sulla partizione di sistema dal produttore del dispositivo.
Non sono in esecuzione app che potrebbero causare l'acquisizione, il controllo o l'esito degli overlay.
appsDetected:
["KNOWN_INSTALLED",
"UNKNOWN_INSTALLED",
"UNKNOWN_CAPTURING"]
Esistono app installate da Google Play o precaricate sulla partizione di sistema dal produttore del dispositivo.
Esistono altre app in esecuzione e per le quali sono abilitate autorizzazioni che potrebbero essere utilizzate per visualizzare la schermata o acquisire altri input e output.
appsDetected:
["KNOWN_INSTALLED",
"KNOWN_CAPTURING",
"UNKNOWN_INSTALLED",
"UNKNOWN_CONTROLLING"]
Sono presenti app o sistemi in esecuzione con autorizzazioni abilitate che potrebbero essere utilizzate per visualizzare la schermata o acquisire altri input e output.
Esistono anche altre app in esecuzione che hanno autorizzazioni attive che potrebbero essere usate per controllare il dispositivo e controllare direttamente gli input nella tua app.
appAccessRiskVerdict: {} Il rischio di accesso all'app non viene valutato perché manca un requisito necessario. Ad esempio, il dispositivo non era abbastanza attendibile.

A seconda del livello di rischio, puoi decidere quale combinazione di esiti accettare per procedere e su quali conseguenze vuoi intervenire. Lo snippet di codice riportato di seguito illustra un esempio di verifica che non siano in esecuzione app in grado di acquisire lo schermo o controllare l'app:

Kotlin

val environmentDetails =
    JSONObject(payload).getJSONObject("environmentDetails")
val appAccessRiskVerdict =
    environmentDetails.getJSONObject("appAccessRiskVerdict")

if (appAccessRiskVerdict.has("appsDetected")) {
    val appsDetected = appAccessRiskVerdict.getJSONArray("appsDetected").toString()
    if (!appsDetected.contains("CAPTURING") && !appsDetected.contains("CONTROLLING")) {
        // Looks good!
    }
}

Java

JSONObject environmentDetails =
    new JSONObject(payload).getJSONObject("environmentDetails");
JSONObject appAccessRiskVerdict =
    environmentDetails.getJSONObject("appAccessRiskVerdict");

if (appAccessRiskVerdict.has("appsDetected")) {
    String appsDetected = appAccessRiskVerdict.getJSONArray("appsDetected").toString()
    if (!appsDetected.contains("CAPTURING") && !appsDetected.contains("CONTROLLING")) {
        // Looks good!
    }
}

Esito Play Protect

Una volta abilitato, il campo environmentDetails nel payload dell'API Play Integrity conterrà l'esito Play Protect:

environmentDetails: {
  playProtectVerdict: "NO_ISSUES"
}

playProtectVerdict può avere uno dei seguenti valori:

NO_ISSUES
Play Protect è attivo e non ha rilevato problemi con le app sul dispositivo.
NO_DATA
Play Protect è attivo, ma non è stata ancora eseguita alcuna scansione. Il dispositivo o l'app Play Store potrebbero essere stati reimpostati di recente.
POSSIBLE_RISK
Play Protect è disattivato.
MEDIUM_RISK
Play Protect è attivo e ha rilevato app potenzialmente dannose installate sul dispositivo.
HIGH_RISK
Play Protect è attivo e ha rilevato app pericolose installate sul dispositivo.
UNEVALUATED

L'esito Play Protect non è stato valutato.

Questo potrebbe accadere per diversi motivi, tra cui:

  • Il dispositivo non è abbastanza attendibile.
  • Solo giochi: l'account utente non dispone di una licenza Google Play per il gioco.

Indicazioni sull'utilizzo dell'esito Play Protect

Il server di backend dell'app può decidere come agire in base all'esito basato sulla tolleranza al rischio. Ecco alcuni suggerimenti e potenziali azioni dell'utente:

NO_ISSUES
Play Protect è attivo e non ha rilevato problemi, quindi non è richiesta alcuna azione da parte dell'utente.
POSSIBLE_RISK e NO_DATA
Quando ricevi questi esiti, chiedi all'utente di verificare che Play Protect sia attivo e che abbia eseguito un'analisi. NO_DATA dovrebbe apparire solo in rare circostanze.
MEDIUM_RISK e HIGH_RISK
A seconda della tua tolleranza al rischio, puoi chiedere all'utente di avviare Play Protect e intervenire sugli avvisi di Play Protect. Se l'utente non riesce a soddisfare questi requisiti, puoi bloccarlo dall'azione del server.