Esegui una richiesta API standard

Questa pagina descrive come effettuare richieste API standard per i giudizi di integrità, che sono supportati su Android 5.0 (livello API 21) o versioni successive. Puoi effettuare una richiesta API standard per un verdetto di integrità ogni volta che la tua app effettua una chiamata al server per verificare se l'interazione è autentica.

Panoramica

Diagramma di sequenza che mostra il design di alto livello dell'API Play Integrity

Una richiesta standard è costituita da due parti:

  • Prepara il provider di token di integrità (una tantum): devi chiamare l'API Integrity per preparare il provider di token di integrità molto prima di dover ottenere il verdetto di integrità. Ad esempio, puoi farlo al momento del lancio dell'app o in background prima che sia necessario il verdetto sull'integrità.
  • Richiedi un token di integrità (su richiesta): ogni volta che la tua app effettua una richiesta al server che vuoi verificare sia autentica, richiedi un token di integrità e invialo al server di backend dell'app per la decrittografia e la verifica. Il server di backend può quindi decidere come agire.

Prepara il provider di token di integrità (una tantum):

  1. L'app chiama il fornitore di token di integrità con il numero del progetto Google Cloud.
  2. L'app memorizza il fornitore del token di integrità per ulteriori chiamate di controllo dell'attestazione.

Richiedi un token di integrità (su richiesta):

  1. Per l'azione utente che deve essere protetta, l'app calcola l'hash (utilizzando un algoritmo di hashing appropriato come SHA256) della richiesta da effettuare.
  2. L'app richiede un token di integrità, passando l'hash della richiesta.
  3. L'app riceve il token di integrità firmato e criptato dall'API Play Integrity.
  4. L'app passa il token di integrità al backend dell'app.
  5. Il backend dell'app invia il token a un server di Google Play. Il server di Google Play decripta e verifica il verdetto, restituendo i risultati al backend della tua app.
  6. Il backend dell'app decide come procedere in base agli indicatori contenuti nel payload del token.
  7. Il backend dell'app invia i risultati della decisione all'app.

Prepara il provider di token di integrità (una tantum)

Prima di effettuare una richiesta standard per un verdetto di integrità da Google Play, devi preparare (o "preriscaldare") il provider di token di integrità. In questo modo, Google Play può memorizzare in modo intelligente nella cache le informazioni di attestazione parziale sul dispositivo per ridurre la latenza sul percorso critico quando effettui una richiesta di un verdetto di integrità. Preparare di nuovo il fornitore di token è un modo per ripetere controlli di integrità meno impegnativi in termini di risorse, che renderanno più aggiornato il prossimo verdetto sull'integrità che richiedi.

Potresti preparare il provider del token di integrità:

  • Quando l'app viene avviata (ad esempio all'avvio a freddo). La preparazione del provider di token è asincrona e pertanto non influisce sul tempo di avvio. Questa opzione è ideale se prevedi di effettuare una richiesta di esito relativo all'integrità poco dopo il lancio dell'app, ad esempio quando un utente accede o un giocatore si unisce a una partita.
  • Quando l'app viene aperta (ad es. all'avvio da un avvio caldo). Tuttavia, tieni presente che ogni istanza dell'app può preparare il token di integrità solo fino a 5 volte al minuto.
  • In qualsiasi momento in background quando vuoi preparare il token in anticipo rispetto a una richiesta di verdetto sull'integrità.

Per preparare il provider di token di integrità:

  1. Crea un StandardIntegrityManager, come mostrato negli esempi seguenti.
  2. Costruisci un PrepareIntegrityTokenRequest, fornendo il numero di progetto Google Cloud tramite il metodo setCloudProjectNumber().
  3. Utilizza il gestore per chiamare prepareIntegrityToken(), fornendo il valore PrepareIntegrityTokenRequest.

Java

import com.google.android.gms.tasks.Task;

// Create an instance of a manager.
StandardIntegrityManager standardIntegrityManager =
    IntegrityManagerFactory.createStandard(applicationContext);

StandardIntegrityTokenProvider integrityTokenProvider;
long cloudProjectNumber = ...;

// Prepare integrity token. Can be called once in a while to keep internal
// state fresh.
standardIntegrityManager.prepareIntegrityToken(
    PrepareIntegrityTokenRequest.builder()
        .setCloudProjectNumber(cloudProjectNumber)
        .build())
    .addOnSuccessListener(tokenProvider -> {
        integrityTokenProvider = tokenProvider;
    })
    .addOnFailureListener(exception -> handleError(exception));

Unity

IEnumerator PrepareIntegrityTokenCoroutine() {
    long cloudProjectNumber = ...;

    // Create an instance of a standard integrity manager.
    var standardIntegrityManager = new StandardIntegrityManager();

    // Request the token provider.
    var integrityTokenProviderOperation =
      standardIntegrityManager.PrepareIntegrityToken(
        new PrepareIntegrityTokenRequest(cloudProjectNumber));

    // Wait for PlayAsyncOperation to complete.
    yield return integrityTokenProviderOperation;

    // Check the resulting error code.
    if (integrityTokenProviderOperation.Error != StandardIntegrityErrorCode.NoError)
    {
        AppendStatusLog("StandardIntegrityAsyncOperation failed with error: " +
                integrityTokenProviderOperation.Error);
        yield break;
    }

    // Get the response.
    var integrityTokenProvider = integrityTokenProviderOperation.GetResult();
}

Nativo

/// Initialize StandardIntegrityManager
StandardIntegrityManager_init(/* app's java vm */, /* an android context */);
/// Create a PrepareIntegrityTokenRequest opaque object.
int64_t cloudProjectNumber = ...;
PrepareIntegrityTokenRequest* tokenProviderRequest;
PrepareIntegrityTokenRequest_create(&tokenProviderRequest);
PrepareIntegrityTokenRequest_setCloudProjectNumber(tokenProviderRequest, cloudProjectNumber);

/// Prepare a StandardIntegrityTokenProvider opaque type pointer and call
/// StandardIntegrityManager_prepareIntegrityToken().
StandardIntegrityTokenProvider* tokenProvider;
StandardIntegrityErrorCode error_code =
        StandardIntegrityManager_prepareIntegrityToken(tokenProviderRequest, &tokenProvider);

/// ...
/// Proceed to polling iff error_code == STANDARD_INTEGRITY_NO_ERROR
if (error_code != STANDARD_INTEGRITY_NO_ERROR)
{
    /// Remember to call the *_destroy() functions.
    return;
}
/// ...
/// Use polling to wait for the async operation to complete.

IntegrityResponseStatus token_provider_status;

/// Check for error codes.
StandardIntegrityErrorCode error_code =
        StandardIntegrityTokenProvider_getStatus(tokenProvider, &token_provider_status);
if (error_code == STANDARD_INTEGRITY_NO_ERROR
    && token_provider_status == INTEGRITY_RESPONSE_COMPLETED)
{
    /// continue to request token from the token provider
}
/// ...
/// Remember to free up resources.
PrepareIntegrityTokenRequest_destroy(tokenProviderRequest);

Proteggere le richieste da manomissioni (opzione consigliata)

Quando controlli un'azione utente nella tua app con l'API Play Integrity, puoi sfruttare il campo requestHash per proteggerti dagli attacchi di manomissione. Ad esempio, un gioco potrebbe voler segnalare il punteggio del giocatore al server di backend del gioco e il tuo server vuole assicurarsi che questo punteggio non sia stato manomesso da un server proxy. L'API Play Integrity restituisce il valore impostato nel requestHash campo, all'interno della risposta di integrità firmata. Senza il valore requestHash, il token di integrità verrà associato solo al dispositivo, ma non alla richiesta specifica, il che apre la possibilità di attacchi. Le seguenti istruzioni descrivono come utilizzare efficacemente il campo requestHash:

Quando richiedi un esito relativo all'integrità:

  • Calcola un digest di tutti i parametri di richiesta pertinenti (ad es. SHA256 di una serializzazione della richiesta stabile) dall'azione utente o dalla richiesta del server in corso. Il valore impostato nel campo requestHash ha una lunghezza massima di 500 byte. Includi in requestHash tutti i dati delle richieste di app fondamentali o pertinenti per l'azione che stai controllando o proteggendo. Il campo requestHash è incluso nel token di integrità verbatim, pertanto i valori lunghi possono aumentare le dimensioni della richiesta.
  • Fornisci il digest come campo requestHash all'API Play Integrity e otteni il token di integrità.

Quando ricevi un esito relativo all'integrità:

  • Decodifica il token di integrità ed estrai il campo requestHash.
  • Calcola un digest della richiesta nello stesso modo in cui viene fatto nell'app (ad es. SHA256 di una serializzazione della richiesta stabile).
  • Confronta i digest lato app e lato server. Se non corrispondono, la richiesta non è attendibile.

Richiedere un esito relativo all'integrità (su richiesta)

Dopo aver preparato il provider di token di integrità, puoi iniziare a richiedere gli esiti di integrità da Google Play. Per farlo, segui questi passaggi:

  1. Ottieni un StandardIntegrityTokenProvider, come mostrato sopra.
  2. Costruisci un StandardIntegrityTokenRequest, fornendo l'hash della richiesta dell'azione utente che vuoi proteggere tramite il metodo setRequestHash.
  3. Utilizza il fornitore di token di integrità per chiamare request(), fornendo il valore StandardIntegrityTokenRequest.

Java

import com.google.android.gms.tasks.Task;

StandardIntegrityTokenProvider integrityTokenProvider;

// See above how to prepare integrityTokenProvider.

// Request integrity token by providing a user action request hash. Can be called
// several times for different user actions.
String requestHash = "2cp24z...";
Task<StandardIntegrityToken> integrityTokenResponse =
    integrityTokenProvider.request(
        StandardIntegrityTokenRequest.builder()
            .setRequestHash(requestHash)
            .build());
integrityTokenResponse
    .addOnSuccessListener(response -> sendToServer(response.token()))
    .addOnFailureListener(exception -> handleError(exception));

Unity

IEnumerator RequestIntegrityTokenCoroutine() {
    StandardIntegrityTokenProvider integrityTokenProvider;

    // See above how to prepare integrityTokenProvider.

    // Request integrity token by providing a user action request hash. Can be called
    // several times for different user actions.
    String requestHash = "2cp24z...";
    var integrityTokenOperation = integrityTokenProvider.Request(
      new StandardIntegrityTokenRequest(requestHash)
    );

    // Wait for PlayAsyncOperation to complete.
    yield return integrityTokenOperation;

    // Check the resulting error code.
    if (integrityTokenOperation.Error != StandardIntegrityErrorCode.NoError)
    {
        AppendStatusLog("StandardIntegrityAsyncOperation failed with error: " +
                integrityTokenOperation.Error);
        yield break;
    }

    // Get the response.
    var integrityToken = integrityTokenOperation.GetResult();
}

Nativo

/// Create a StandardIntegrityTokenRequest opaque object.
const char* requestHash = ...;
StandardIntegrityTokenRequest* tokenRequest;
StandardIntegrityTokenRequest_create(&tokenRequest);
StandardIntegrityTokenRequest_setRequestHash(tokenRequest, requestHash);

/// Prepare a StandardIntegrityToken opaque type pointer and call
/// StandardIntegrityTokenProvider_request(). Can be called several times for
/// different user actions. See above how to prepare token provider.
StandardIntegrityToken* token;
StandardIntegrityErrorCode error_code =
        StandardIntegrityTokenProvider_request(tokenProvider, tokenRequest, &token);

/// ...
/// Proceed to polling iff error_code == STANDARD_INTEGRITY_NO_ERROR
if (error_code != STANDARD_INTEGRITY_NO_ERROR)
{
    /// Remember to call the *_destroy() functions.
    return;
}
/// ...
/// Use polling to wait for the async operation to complete.

IntegrityResponseStatus token_status;

/// Check for error codes.
StandardIntegrityErrorCode error_code =
        StandardIntegrityToken_getStatus(token, &token_status);
if (error_code == STANDARD_INTEGRITY_NO_ERROR
    && token_status == INTEGRITY_RESPONSE_COMPLETED)
{
    const char* integrityToken = StandardIntegrityToken_getToken(token);
}
/// ...
/// Remember to free up resources.
StandardIntegrityTokenRequest_destroy(tokenRequest);
StandardIntegrityToken_destroy(token);
StandardIntegrityTokenProvider_destroy(tokenProvider);
StandardIntegrityManager_destroy();

Decripta e verifica l'esito relativo all'integrità

Dopo aver richiesto un esito relativo all'integrità, l'API Play Integrity fornisce un token di risposta criptato. Per ottenere gli esiti relativi all'integrità del dispositivo, devi decriptare il token di integrità sui server di Google. Per farlo, segui questi passaggi:

  1. Crea un account di servizio nel progetto Google Cloud collegato alla tua app.
  2. Sul server dell'app, recupera il token di accesso dalle credenziali del tuo account servizio utilizzando l'ambito playintegrity ed effettua la seguente richiesta:

    playintegrity.googleapis.com/v1/PACKAGE_NAME:decodeIntegrityToken -d \
    '{ "integrity_token": "INTEGRITY_TOKEN" }'
  3. Leggi la risposta JSON.

Il payload risultante è un token in testo normale che contiene verdict sull'integrità.

Protezione da ripetizione automatica

Per ridurre gli attacchi di replay, Google Play garantisce automaticamente che ogni token di integrità non possa essere riutilizzato più volte. Il tentativo di decriptare ripetutamente lo stesso token comporterà giudizi chiari. Per i token protetti da replay, i giudizi decodificati vengono restituiti come segue:

  • Il verdetto sul riconoscimento del dispositivo sarà vuoto.
  • L'esito del riconoscimento app e l'esito relativo alle licenze dell'app verranno impostati su UNEVALUATED.
  • Qualsiasi verdetto facoltativo abilitato tramite Play Console verrà impostato su UNEVALUATED (o su un verdetto vuoto se si tratta di un verdetto con più valori).