Tworzenie standardowego żądania do interfejsu API

Na tej stronie opisaliśmy wysyłanie standardowych żądań interfejsu API w celu uzyskania werdyktu integralności. Są one obsługiwane w Androidzie 5.0 (poziom interfejsu API 21) i nowszych. Aby sprawdzić, czy interakcja jest autentyczna, możesz wysłać standardowe żądanie interfejsu API dotyczące oceny integralności, gdy aplikacja wysyła żądanie do serwera.

Omówienie

Diagram sekwencji przedstawiający ogólny projekt interfejsu Play Integrity API

Standardowe żądanie składa się z 2 części:

  • Przygotowanie dostawcy tokena integralności (jednorazowo): musisz wywołać interfejs API Integrity, aby przygotować dostawcę tokena integralności na długo przed uzyskaniem oceny integralności. Możesz to zrobić na przykład podczas uruchamiania aplikacji lub w tle, zanim będzie potrzebne orzeczenie dotyczące integralności.
  • Poproś o token integralności (na żądanie): gdy aplikacja wysyła żądanie serwera, którego autentyczność chcesz sprawdzić, żądasz tokena integralności i wysyłasz go do serwera backendu aplikacji w celu odszyfrowania i weryfikacji. Następnie serwer backendu może podjąć odpowiednie działania.

Przygotuj dostawcę tokena integralności (jednorazowo):

  1. Aplikacja wywołuje dostawcę tokena integralności z numerem projektu Google Cloud.
  2. Aplikacja przechowuje w pamięci informacje o dostawcy tokena integralności na potrzeby dalszych wywołań funkcji weryfikacji.

Poproś o token integralności (na żądanie):

  1. W przypadku działania użytkownika, które wymaga ochrony, aplikacja oblicza szyfr (za pomocą dowolnego odpowiedniego algorytmu szyfrowania, np. SHA256) dla danego żądania.
  2. Aplikacja prosi o token integralności, przekazując hasz żądania.
  3. Twoja aplikacja otrzymuje podpisany i zaszyfrowany token integralności od interfejsu Play Integrity API.
  4. Aplikacja przekazuje token integralności do backendu.
  5. Serwer backendu aplikacji wysyła token na serwer Google Play. Serwer Google Play odszyfrowuje i weryfikuje werdykt, a potem zwraca wyniki do backendu aplikacji.
  6. Serwer backendu aplikacji podejmuje decyzję na podstawie sygnałów zawartych w ładunku tokenu.
  7. Backend aplikacji wysyła wyniki decyzji do aplikacji.

Przygotuj dostawcę tokena integralności (jednorazowo)

Zanim prześlesz standardowe żądanie oceny integralności z Google Play, musisz przygotować (czyli „rozgrzać”) dostawcę tokenów integralności. Dzięki temu Google Play może inteligentnie przechowywać w pamięci podręcznej na urządzeniu częściowe informacje o weryfikacji, aby zmniejszyć opóźnienie na ścieżce krytycznej, gdy wysyłasz żądanie dotyczące orzeczenia o integralności. Przygotowanie dostawcy tokenów jest sposobem na powtórzenie weryfikacji integralności, które nie wymagają dużych zasobów, dzięki czemu następny wynik weryfikacji integralności będzie bardziej aktualny.

Dostawcę tokena integralności możesz przygotować:

  • podczas uruchamiania aplikacji (czyli w przypadku uruchomienia „na zimno”). Przygotowanie dostawcy tokenów odbywa się asynchronicznie, więc nie wpłynie na czas uruchamiania. Ta opcja sprawdzi się, jeśli planujesz wysłać prośbę o ocenę integralności zaraz po uruchomieniu aplikacji, np. gdy użytkownik zaloguje się lub gracz dołączy do gry.
  • gdy aplikacja jest otwarta (np. podczas uruchamiania z pamięci); Pamiętaj jednak, że każda instancja aplikacji może przygotować token integralności tylko do 5 razy na minutę.
  • W dowolnym momencie w tle, gdy chcesz przygotować token z wyprzedzeniem w ramach prośby o ogłoszenie werdyktu dotyczącego integralności.

Aby przygotować dostawcę tokena integralności:

  1. Utwórz StandardIntegrityManager, jak pokazano w tych przykładach.
  2. Utwórz obiekt PrepareIntegrityTokenRequest, podając numer projektu Google Cloud za pomocą metody setCloudProjectNumber().
  3. Zadzwoń do prepareIntegrityToken() za pomocą menedżera, podając 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();
}

Rodzimy użytkownik

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

Ochrona żądań przed modyfikacją (zalecana)

Podczas sprawdzania działania użytkownika w aplikacji za pomocą interfejsu Play Integrity API możesz wykorzystać pole requestHash, aby ograniczyć ryzyko ataków polegających na modyfikacji aplikacji. Na przykład gra może chcieć przekazać wynik gracza do serwera backendowego, a serwer chce się upewnić, że wynik nie został zmieniony przez serwer proxy. Interfejs Play Integrity API zwraca wartość ustawioną w polu requestHash w ramach podpisanej odpowiedzi interfejsu Integrity API. Bez tego parametru token integralności będzie powiązany tylko z urządzeniem, a nie z konkretnym żądaniem, co stwarza możliwość ataku.requestHash Z tych instrukcji dowiesz się, jak skutecznie korzystać z pola requestHash:

Gdy żądasz oceny integralności:

  • Oblicz digest wszystkich odpowiednich parametrów żądania (np. SHA256 stabilnej serializacji żądania) na podstawie działania użytkownika lub żądania serwera, które jest realizowane. Wartość ustawiona w polu requestHash może mieć maksymalnie 500 bajtów. W polu requestHash podaj wszelkie dane żądania aplikacji, które są kluczowe lub istotne dla działania, które sprawdzasz lub chronisz. Pole requestHash jest dołączane do tokena integralności w niezmienionej postaci, więc długie wartości mogą zwiększać rozmiar żądania.
  • Prześlij treść skrótu jako pole requestHash do interfejsu Play Integrity API i pobierz token integralności.

Gdy otrzymasz ocenę integralności:

  • Odkoduj token integralności i wyodrębnij pole requestHash.
  • Oblicz digest żądania w taki sam sposób jak w aplikacji (np. SHA256 z użyciem stabilnej serializacji żądania).
  • Porównaj skróty po stronie aplikacji i po stronie serwera. Jeśli się nie zgadzają, prośba nie jest wiarygodna.

Prośba o ocenę integralności (na żądanie)

Gdy przygotujesz dostawcę tokenów integralności, możesz zacząć żądać ocen integralności z Google Play. Aby to zrobić:

  1. Uzyskaj StandardIntegrityTokenProvider, jak pokazano powyżej.
  2. Utwórz StandardIntegrityTokenRequest, podając hasz żądania działania użytkownika, które chcesz chronić za pomocą metody setRequestHash.
  3. Użyj dostawcy tokena integralności, aby wywołać funkcję request(), podając argument 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();
}

Rodzimy użytkownik

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

Odszyfruj i sprawdź ocenę integralności

Po wysłaniu żądania o ocenę integralności interfejs Play Integrity API udostępnia zaszyfrowany token odpowiedzi. Aby uzyskać oceny integralności urządzenia, musisz odszyfrować token integralności na serwerach Google. Aby to zrobić, wykonaj te czynności:

  1. Utwórz konto usługi w projekcie Google Cloud połączonym z Twoją aplikacją.
  2. Na serwerze aplikacji pobierz token dostępu z uprawnień konta usługi za pomocą zakresu playintegrity i wyślij to żądanie:

    playintegrity.googleapis.com/v1/PACKAGE_NAME:decodeIntegrityToken -d \
    '{ "integrity_token": "INTEGRITY_TOKEN" }'
  3. Przeczytaj odpowiedź w formacie JSON.

Wynikiem jest token w postaci zwykłego tekstu, który zawiera werdykty dotyczące integralności.

Automatyczna ochrona przed odtwarzaniem

Aby zapobiec atakom polegającym na odtwarzaniu, Google Play automatycznie sprawdza, czy dany token integralności nie jest używany wielokrotnie. Próby wielokrotnego odszyfrowania tego samego tokena spowodują, że wyroki będą czyste. W przypadku tokenów chronionych przed odtwarzaniem zwracane są następujące odkodowane werdykty:

  • Wynik rozpoznawania urządzenia będzie pusty.
  • Ocena rozpoznawania aplikacji i ocena licencjonowania aplikacji zostaną ustawione na UNEVALUATED.
  • Wszystkie opcjonalne werdykty włączone w Konsoli Play zostaną ustawione na UNEVALUATED (lub na pusty werdykt, jeśli jest to werdykt wielowartościowy).