Envoyer une requête API standard

Cette page explique comment effectuer des requêtes API standards pour des évaluations de l'intégrité sous Android 5.0 (niveau d'API 21) ou version ultérieure. Vous pouvez envoyer une requête API standard pour une évaluation de l'intégrité chaque fois que votre application effectue un appel vers le serveur pour vérifier si l'interaction est authentique.

Présentation

Schéma séquentiel illustrant la conception générale de l'API Play Integrity

Une requête standard comprend deux étapes :

  • Préparer le fournisseur de jetons d'intégrité (une seule fois) : vous devez appeler l'API Integrity pour préparer le fournisseur de jetons d'intégrité bien avant d'obtenir l'évaluation de l'intégrité. Par exemple, vous pouvez le faire lors du lancement de votre application ou en arrière-plan avant de procéder à l'évaluation de l'intégrité.
  • Demander un jeton d'intégrité (à la demande) : chaque fois que votre application effectue une requête de serveur que vous souhaitez vérifier, vous demandez un jeton d'intégrité et l'envoyez au serveur backend de votre application pour qu'il le déchiffre et le valide. Votre serveur backend pourra ensuite décider de la procédure à suivre.

Préparer le fournisseur de jetons d'intégrité (une seule fois) :

  1. Votre application appelle le fournisseur de jetons d'intégrité avec votre numéro de projet Google Cloud.
  2. Votre application conserve en mémoire le fournisseur de jetons d'intégrité pour les futurs appels de vérification d'attestation.

Demander un jeton d'intégrité (à la demande) :

  1. Pour l'action utilisateur devant être protégée, votre application calcule le hachage (à l'aide d'un algorithme de hachage approprié tel que SHA256) de la requête à effectuer.
  2. Votre application demande un jeton d'intégrité, en transmettant le hachage de la requête.
  3. Votre application reçoit le jeton d'intégrité signé et chiffré de la part de l'API Play Integrity.
  4. Votre application transmet le jeton d'intégrité au backend.
  5. Le backend de votre application envoie le jeton à un serveur Google Play. Le serveur Google Play déchiffre et vérifie l'évaluation, puis renvoie les résultats au backend de votre application.
  6. Le backend de votre application décide de la marche à suivre en fonction des signaux qui se trouvent dans la charge utile du jeton.
  7. Le backend de votre application envoie les résultats de la décision à votre application.

Préparer le fournisseur de jetons d'intégrité (une seule fois)

Avant d'envoyer une demande standard d'évaluation de l'intégrité par Google Play, vous devez préparer le fournisseur de jetons d'intégrité. Cela permet à Google Play de mettre en cache intelligemment les informations d'attestation partielle sur l'appareil, afin de réduire la latence sur le chemin critique lorsque vous effectuez une requête d'évaluation de l'intégrité Préparer à nouveau le fournisseur de jetons permet de répéter des contrôles d'intégrité moins intensifs en termes de ressources. La prochaine évaluation de l'intégrité que vous demanderez sera ainsi plus à jour.

Vous pouvez préparer le fournisseur de jetons d'intégrité dans les circonstances suivantes :

  • Au lancement de votre application (au démarrage à froid). La préparation du fournisseur de jetons est asynchrone et n'aura donc aucune incidence sur le temps de démarrage. Cette option fonctionne bien si vous prévoyez d'envoyer une requête d'évaluation de l'intégrité peu de temps après le lancement de l'application, par exemple lorsqu'un utilisateur se connecte ou qu'un joueur rejoint un jeu.
  • Lorsque l'application est ouverte (au démarrage à chaud). Notez toutefois que chaque instance d'application ne peut préparer le jeton d'intégrité que cinq fois par minute.
  • À tout moment en arrière-plan, lorsque vous souhaitez préparer le jeton avant une requête d'évaluation de l'intégrité.

Pour préparer le fournisseur de jetons d'intégrité, procédez comme suit :

  1. Créez un StandardIntegrityManager, comme illustré dans les exemples suivants.
  2. Créez une PrepareIntegrityTokenRequest en fournissant le numéro de projet Google Cloud via la méthode setCloudProjectNumber().
  3. Utilisez le gestionnaire pour appeler prepareIntegrityToken(), en fournissant 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();
}

Natif

/// 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);

Protéger les requêtes contre la falsification (recommandé)

Lorsque vous vérifiez une action de l'utilisateur dans votre application avec l'API Play Integrity, vous pouvez exploiter le champ requestHash pour limiter les attaques par falsification. Par exemple, un jeu peut vouloir communiquer le score du joueur à son serveur backend, et votre serveur veut s'assurer que ce score n'a pas été modifié par un serveur proxy. L'API Play Integrity renvoie la valeur que vous avez définie dans le champ requestHash, dans la réponse d'intégrité signée. Sans le champ requestHash, le jeton d'intégrité ne sera lié qu'à l'appareil, et non à la requête spécifique, ce qui n'exclut pas le risque d'attaque. Les instructions suivantes décrivent comment utiliser efficacement le champ requestHash :

Lorsque vous demandez une évaluation de l'intégrité, procédez comme suit :

  • Calculez un récapitulatif de tous les paramètres de requête pertinents (par exemple, SHA256 d'une sérialisation de requête stable) à partir de l'action de l'utilisateur ou de la requête de serveur en cours. La valeur définie dans le champ requestHash ne doit pas dépasser 500 octets. Incluez dans le champ requestHash toutes les données de requête d'application essentielles ou pertinentes pour l'action que vous vérifiez ou protégez. Le champ requestHash est inclus mot à mot dans le jeton d'intégrité. Par conséquent, les valeurs longues peuvent augmenter la taille de la requête.
  • Fournissez le récapitulatif en tant que champ requestHash à l'API Play Integrity et obtenez le jeton d'intégrité.

Lorsque vous recevez une évaluation de l'intégrité, procédez comme suit :

  • Décodez le jeton d'intégrité et extrayez le champ requestHash.
  • Calculez un récapitulatif de la requête de la même manière que dans l'application (par exemple, SHA256 d'une sérialisation de requête stable).
  • Comparez les récapitulatifs côté application et côté serveur. S'ils ne correspondent pas, la requête n'est pas fiable.

Demander une évaluation de l'intégrité (à la demande)

Une fois que vous avez préparé le fournisseur de jetons d'intégrité, vous pouvez commencer à demander des évaluations de l'intégrité par Google Play. Pour ce faire, procédez comme suit :

  1. Obtenez un StandardIntegrityTokenProvider, comme indiqué ci-dessus.
  2. Créez une StandardIntegrityTokenRequest en fournissant le hachage de la requête de l'action utilisateur que vous souhaitez protéger via la méthode setRequestHash.
  3. Utilisez le fournisseur de jetons d'intégrité pour appeler request(), en fournissant la 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();
}

Natif

/// 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();

Déchiffrer et vérifier l'évaluation de l'intégrité

Une fois que vous avez demandé l'évaluation de l'intégrité, l'API Play Integrity fournit un jeton de réponse chiffré. Pour obtenir les évaluations de l'intégrité de l'appareil, vous devez déchiffrer le jeton d'intégrité sur les serveurs de Google. Pour cela, procédez comme suit :

  1. Créez un compte de service dans le projet Google Cloud associé à votre application. Lors de la création du compte, vous devez attribuer à votre compte de service les rôles d'utilisateur du compte de service et de consommateur de Service Usage.
  2. Sur le serveur de votre application, récupérez le jeton d'accès à partir des identifiants de votre compte de service à l'aide du champ d'application playintegrity, puis exécutez la requête suivante :

    playintegrity.googleapis.com/v1/PACKAGE_NAME:decodeIntegrityToken -d \
    '{ "integrity_token": "INTEGRITY_TOKEN" }'
  3. Lisez la réponse JSON.

La charge utile résultante est un jeton en texte brut contenant des évaluations de l'intégrité.

Protection automatique contre le rejeu

Pour limiter les attaques par rejeu, Google Play s'assure automatiquement que chaque jeton d'intégrité ne peut pas être réutilisé plusieurs fois. Si vous tentez de déchiffrer à plusieurs reprises le même jeton, les évaluations seront vides.