Fazer uma solicitação de API padrão

Esta página descreve como fazer solicitações de API padrão para vereditos de integridade, que têm suporte no Android 5.0 (nível 21 da API) ou versões mais recentes. É possível fazer uma solicitação de API padrão para um veredito de integridade sempre que o app estiver fazendo uma chamada do servidor para verificar se a interação é genuína.

Informações gerais

Diagrama de sequência que mostra o design de alto nível da API Play
Integrity

Uma solicitação padrão consiste em duas partes:

  • Preparar o provedor de token de integridade (único): é necessário chamar a API Integrity para preparar o provedor de token de integridade bem antes de receber o veredito de integridade. Por exemplo, você pode fazer isso quando seu app for iniciado ou em segundo plano antes de precisar do veredito de integridade.
  • Solicitar um token de integridade (sob demanda): sempre que o app fizer uma solicitação de servidor que você queira verificar se é genuína, solicite um token de integridade e o envie ao servidor de back-end do app para descriptografia e verificação. Em seguida, o servidor de back-end pode decidir como agir.

Preparar o provedor de token de integridade (único):

  1. O app chama o provedor do token de integridade com o número do projeto do Google Cloud.
  2. O app mantém o provedor de token de integridade na memória para outras chamadas de verificação de atestado.

Solicitar um token de integridade (sob demanda):

  1. Para a ação do usuário que precisa ser protegida, o app calcula o hash (usando o algoritmo de hash adequado, como SHA256) da solicitação a ser feita.
  2. O app solicita um token de integridade, transmitindo o hash de solicitação.
  3. O app recebe o token de integridade assinado e criptografado da API Play Integrity.
  4. O app transmite o token de integridade para o back-end.
  5. O back-end do app envia o token a um servidor do Google Play. O servidor do Google Play descriptografa e verifica o veredito, retornando os resultados ao back-end do app.
  6. O back-end do app decide como proceder, de acordo com os sinais contidos no payload do token.
  7. O back-end do app envia os resultados da decisão para o app.

Preparar o provedor de token de integridade (único)

Antes de fazer uma solicitação padrão para um veredito de integridade do Google Play, é necessário preparar (ou "aquecer") o provedor de token de integridade. Isso permite que o Google Play armazene em cache de forma inteligente informações parciais de atestado no dispositivo para diminuir a latência no caminho crítico quando você faz uma solicitação de veredito de integridade. Preparar o provedor de token novamente é uma forma de repetir menos verificações de integridade pesadas. Isso fará com que o próximo veredito de integridade solicitado esteja mais atualizado.

Prepare o provedor de token de integridade:

  • Quando o app for iniciado (ou seja, na inicialização a frio). A preparação do provedor de token é assíncrona e, portanto, não afeta o tempo de inicialização. Essa opção funcionaria bem se você quisesse fazer uma solicitação de veredito de integridade logo após o app ser iniciado, por exemplo, quando um usuário faz login ou um jogador entra em um jogo.
  • Quando o app for aberto (por exemplo, na inicialização com estado salvo). No entanto, cada instância do app só pode preparar o token de integridade até cinco vezes por minuto.
  • A qualquer momento em segundo plano, quando você quiser preparar o token antes de uma solicitação de veredito de integridade.

Para preparar o provedor de token de integridade, faça o seguinte:

  1. Crie um StandardIntegrityManager, conforme mostrado nos exemplos abaixo.
  2. Crie uma PrepareIntegrityTokenRequest, fornecendo o número do projeto do Google Cloud com o método setCloudProjectNumber().
  3. Use o gerenciador para chamar prepareIntegrityToken(), fornecendo a 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);

Proteger solicitações contra adulteração (recomendado)

Ao verificar uma ação do usuário no app com a API Play Integrity, você pode usar o campo requestHash para mitigar ataques de adulteração. Por exemplo, um jogo talvez queira informar a pontuação do jogador ao servidor de back-end do jogo, e seu servidor quer garantir que ela não seja adulterada por um servidor proxy. A API Play Integrity retorna o valor definido no campo requestHash na resposta de integridade assinada. Sem o requestHash, o token de integridade será vinculado apenas ao dispositivo, mas não à solicitação específica, o que abre a possibilidade de ataque. As instruções abaixo descrevem como usar o campo requestHash com eficiência:

Quando você solicita um veredito de integridade:

  • Calcule um resumo de todos os parâmetros de solicitação relevantes (por exemplo, SHA256 de uma serialização de solicitações estável) da ação do usuário ou da solicitação do servidor que estiver acontecendo. O valor definido no campo requestHash tem um comprimento máximo de 500 bytes. Inclua todos os dados de solicitação de app no requestHash que sejam cruciais ou relevantes para a ação que você está verificando ou protegendo. O campo requestHash é incluído na íntegra no token de integridade. Valores longos podem aumentar o tamanho da solicitação.
  • Forneça o resumo como o campo requestHash à API Play Integrity e extraia o token de integridade.

Quando você recebe um veredito de integridade:

  • Decodifique o token de integridade e extraia o campo requestHash.
  • Calcule um resumo da solicitação da mesma maneira que no app (por exemplo SHA256 de uma serialização de solicitações estável).
  • Compare os resumos do lado do app e do servidor. Caso eles não sejam iguais, a solicitação não é confiável.

Solicitar um veredito de integridade (sob demanda)

Depois de preparar o provedor de token de integridade, você pode começar a solicitar vereditos de integridade no Google Play. Para isso, siga estas etapas:

  1. Extraia um StandardIntegrityTokenProvider, conforme mostrado acima.
  2. Crie uma StandardIntegrityTokenRequest, fornecendo o hash de solicitação da ação do usuário que você quer proteger usando o método setRequestHash.
  3. Use o provedor de token de integridade para chamar request(), fornecendo a 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();

Descriptografar e verificar o veredito de integridade

Depois de solicitar um veredito de integridade, a API Play Integrity fornece um token de resposta criptografado. Para extrair os vereditos de integridade do dispositivo, é necessário descriptografar o token de integridade nos servidores do Google. Para fazer isso, siga estas etapas:

  1. Crie uma conta de serviço no projeto do Google Cloud que está vinculado ao app. Durante o processo de criação, é necessário conceder à conta os papéis de Usuário da conta de serviço e Consumidor do Service Usage.
  2. No servidor do app, busque o token de acesso nas credenciais da conta de serviço usando o escopo playintegrity e faça esta solicitação:

    playintegrity.googleapis.com/v1/PACKAGE_NAME:decodeIntegrityToken -d \
    '{ "integrity_token": "INTEGRITY_TOKEN" }'
  3. Leia a resposta JSON.

O payload resultante é um token de texto simples que contém vereditos de integridade.

Proteção automática contra ataques de repetição

Para reduzir os ataques de repetição, o Google Play garante automaticamente que cada token de integridade não possa ser reutilizado várias vezes. A tentativa de descriptografar repetidamente o mesmo token resulta em vereditos vazios.