Cómo realizar una solicitud a la API estándar

En esta página, se describe cómo realizar solicitudes a la API estándar para obtener veredictos de integridad, que se admiten en Android 5.0 (nivel de API 21) o versiones posteriores. Puedes realizar una solicitud a la API estándar para obtener un veredicto de integridad cuando tu app realice una llamada al servidor para verificar que la interacción sea original.

Descripción general

Diagrama de secuencia que muestra el diseño general de la API de Play Integrity.

Una solicitud estándar consta de dos partes:

  • Prepara el proveedor de tokens de integridad (una sola vez): Debes llamar a la API de Integrity para preparar el proveedor de tokens de integridad mucho antes de que necesites obtener el veredicto de integridad. Por ejemplo, puedes hacerlo cuando se inicia la app o en segundo plano antes de que se necesite el veredicto de integridad.
  • Solicitar un token de integridad (a pedido): Cada vez que tu app realiza una solicitud del servidor, la cual quieres verificar si es original, debes solicitar un token de integridad y enviarlo al servidor de backend de tu app para su desencriptación y verificación. Luego, tu servidor de backend puede decidir cómo actuar.

Cómo preparar el proveedor de token de integridad (una sola vez):

  1. Tu app llama al proveedor de tokens de integridad con el número de proyecto de Google Cloud.
  2. Tu app conserva el proveedor de tokens de integridad en la memoria para realizar más llamadas de verificación de certificación.

Cómo solicitar un token de integridad (a pedido):

  1. Para la acción del usuario que se debe proteger, tu app calcula el hash (utilizando cualquier algoritmo de hash adecuado, como SHA256) de la solicitud que se debe realizar.
  2. Tu app solicita un token de integridad y pasa el hash de la solicitud.
  3. Tu app recibe el token de integridad firmado y encriptado de la API de Play Integrity.
  4. Tu app pasa el token de integridad al backend de la app.
  5. El backend de tu app envía el token a un servidor de Google Play. El servidor de Google Play desencripta y verifica el veredicto, y muestra los resultados en el backend de la app.
  6. El backend de la app decide el procedimiento, según los indicadores contenidos en la carga útil del token.
  7. El backend de tu app envía los resultados de la decisión a la app.

Cómo preparar el proveedor de token de integridad (una sola vez)

Antes de realizar una solicitud estándar de un veredicto de integridad de Google Play, debes preparar (o "precalentar") el proveedor de tokens de integridad. Esto permite que Google Play almacene en la caché del dispositivo la información de certificación parcial de forma inteligente para disminuir la latencia en la ruta de acceso crítica cuando solicitas un veredicto de integridad. Preparar nuevamente al proveedor de tokens es una forma de repetir menos verificaciones de integridad que tendrían un alto consumo de recursos para que el próximo veredicto de integridad que solicites esté más actualizado.

Puedes preparar el proveedor de tokens de integridad de la siguiente manera:

  • Cuando se inicia la app (es decir, cuando se inicia en frío). La preparación del proveedor de tokens es asíncrona, por lo que no afectará el tiempo de inicio. Esta opción funcionaría bien si tienes previsto realizar una solicitud de veredicto de integridad poco después del lanzamiento de la app, por ejemplo, cuando un usuario accede o un jugador se une a un juego.
  • Cuando se abre la app (es decir, cuando se inicia semicaliente). Sin embargo, ten en cuenta que cada instancia de la app solo puede preparar el token de integridad hasta 5 veces por minuto.
  • En cualquier momento en segundo plano, cuando quieras preparar el token antes de una solicitud de veredicto de integridad.

Para preparar el proveedor de tokens de integridad, haz lo siguiente:

  1. Crea un StandardIntegrityManager, como se muestra en los ejemplos que aparecen a continuación.
  2. Crea una PrepareIntegrityTokenRequest y proporciona el número de proyecto de Google Cloud a través del método setCloudProjectNumber().
  3. Usa el administrador para llamar a prepareIntegrityToken(), y proporciona el 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);

Cómo proteger las solicitudes contra la manipulación (recomendado)

Cuando verifiques una acción del usuario en tu app con la API de Play Integrity, puedes aprovechar el campo requestHash para mitigar los ataques de manipulación. Por ejemplo, es posible que un juego quiera informar la puntuación del jugador al servidor de backend del juego, y que tu servidor quiera asegurarse de que esta puntuación no haya sido manipulada por un servidor proxy. La API de Play Integrity muestra el valor que estableces en el campo requestHash, dentro de la respuesta de integridad firmada. Sin el requestHash, el token de integridad estará vinculado solo al dispositivo, pero no a la solicitud específica, lo que abre la posibilidad de un ataque. En las siguientes instrucciones, se describe cómo usar el campo requestHash de manera efectiva:

Cuando solicitas un veredicto de integridad, debes hacer lo siguiente:

  • Calcula un resumen de todos los parámetros de solicitud relevantes (p. ej., SHA256 de una serialización de solicitud estable) de la acción del usuario o la solicitud del servidor que está sucediendo. El valor establecido en el campo requestHash tiene una longitud máxima de 500 bytes. Incluye cualquier dato de solicitud de app en el requestHash que sea fundamental o relevante para la acción que estás verificando o protegiendo. El campo requestHash se incluye de forma literal en el token de integridad, por lo que los valores largos pueden aumentar el tamaño de la solicitud.
  • Proporciona el resumen como el campo requestHash a la API de Play Integrity y obtén el token de integridad.

Cuando recibes un veredicto de integridad, debes hacer lo siguiente:

  • Decodifica el token de integridad y extrae el campo requestHash.
  • Calcula un resumen de la solicitud de la misma manera que en la app (p. ej., SHA256 de una serialización de solicitud estable).
  • Compara los resúmenes de la app y del servidor. Si no coinciden, la solicitud no es confiable.

Cómo solicitar un veredicto de integridad (a pedido)

Después de preparar el proveedor de tokens de integridad, puedes comenzar a solicitar veredictos de integridad de Google Play. Para ello, completa los siguientes pasos:

  1. Obtén un objeto StandardIntegrityTokenProvider, como se muestra más arriba.
  2. Crea una StandardIntegrityTokenRequest y proporciona el hash de solicitud de la acción del usuario que deseas proteger a través del método setRequestHash.
  3. Usa el proveedor de tokens de integridad para llamar a request() y proporciona el 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();

Cómo desencriptar y verificar el veredicto de integridad

Después de solicitar un veredicto de integridad, la API de Play Integrity proporciona un token de respuesta encriptado. Para obtener los veredictos de integridad del dispositivo, debes desencriptar el token de integridad en los servidores de Google. Para hacerlo, completa estos pasos:

  1. Crea una cuenta de servicio dentro del proyecto de Google Cloud que esté vinculado a tu app. Durante este proceso de creación de la cuenta, debes otorgar a tu cuenta de servicio los roles de usuario de cuenta de servicio y consumidor de Service Usage.
  2. En el servidor de la app, recupera el token de acceso de las credenciales de la cuenta de servicio con el permiso playintegrity y realiza la siguiente solicitud:

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

La carga útil resultante es un token de texto sin formato que contiene veredictos de integridad.

Protección contra la repetición automática

Para mitigar los ataques de repetición, Google Play se asegura automáticamente de que cada token de integridad no se pueda volver a usar muchas veces. Si intentas desencriptar el mismo token de forma repetida, obtendrás veredictos vacíos.