Tworzenie standardowego żądania do interfejsu API

Na tej stronie opisujemy wysyłanie standardowych żądań interfejsu API dotyczących wyników integralności, które są obsługiwane na Androidzie 5.0 (poziom interfejsu API 21) i nowszych wersjach. Możesz wysyłać standardowe żądanie do interfejsu API, aby uzyskać ocenę integralności, za każdym razem, gdy aplikacja wysyła wywołanie serwera, aby sprawdzić, czy interakcja jest autentyczna.

Omówienie

Rysunek 1. Diagram sekwencji przedstawiający ogólną architekturę interfejsu Play Integrity API.

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

  • Przygotuj dostawcę tokena integralności (jednorazowo): musisz wywołać interfejs Integrity API, 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 potrzebny wynik weryfikacji.
  • Żądanie tokena integralności (na żądanie): za każdym razem, gdy aplikacja wysyła żądanie serwera, które chcesz sprawdzić pod kątem autentyczności, żądasz tokena integralności i wysyłasz go do serwera backendu aplikacji w celu odszyfrowania i zweryfikowania. Serwer backendu może następnie podjąć odpowiednie działania.

Przygotuj dostawcę tokenów integralności (jednorazowo):

  1. Aplikacja wywołuje dostawcę tokena integralności, podając numer projektu Google Cloud.
  2. Aplikacja przechowuje dostawcę tokena integralności w pamięci na potrzeby kolejnych wywołań sprawdzania atestu.

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

  1. W przypadku działania użytkownika, które wymaga ochrony, aplikacja oblicza skrót (przy użyciu dowolnego odpowiedniego algorytmu szyfrowania, np. SHA256) żądania, które ma zostać wysłane.
  2. Aplikacja wysyła żądanie tokena integralności, przekazując skrót żądania.
  3. Twoja aplikacja otrzymuje podpisany i zaszyfrowany token integralności z interfejsu Play Integrity API.
  4. Aplikacja przekazuje token integralności do backendu.
  5. Backend aplikacji wysyła token na serwer Google Play. Serwer Google Play odszyfrowuje i weryfikuje wynik, a następnie zwraca go do backendu aplikacji.
  6. Backend aplikacji określa sposób postępowania na podstawie sygnałów zawartych w ładunku tokena.
  7. Backend aplikacji przesyła wyniki decyzji do aplikacji.

Przygotowanie dostawcy tokenów integralności (jednorazowo)

Zanim wyślesz standardową prośbę o ocenę integralności do Google Play, musisz przygotować (lub „rozgrzać”) dostawcę tokenów integralności. Dzięki temu Google Play może inteligentnie buforować na urządzeniu częściowe informacje o atestowaniu, aby skrócić czas oczekiwania na krytycznej ścieżce, gdy przesyłasz żądanie dotyczące wyniku weryfikacji integralności. Ponowne przygotowanie dostawcy tokenów to sposób na powtórzenie mniej wymagających zasobów kontroli integralności, dzięki czemu następna ocena integralności, o którą poprosisz, będzie bardziej aktualna.

Możesz przygotować dostawcę tokenów integralności:

  • Gdy aplikacja się uruchamia (czyli przy uruchomieniu „na zimno”). Przygotowanie dostawcy tokena jest asynchroniczne, więc nie wpłynie na czas uruchamiania. Ta opcja sprawdzi się, jeśli planujesz wysłać prośbę o ocenę integralności krótko po uruchomieniu aplikacji, np. gdy użytkownik się zaloguje lub gracz dołączy do gry.
  • Gdy aplikacja jest otwierana (np. przy uruchamianiu „na ciepło”). Pamiętaj jednak, że każda instancja aplikacji może przygotować token integralności maksymalnie 5 razy na minutę.
  • W dowolnym momencie w tle, gdy chcesz przygotować token z wyprzedzeniem przed wysłaniem żądania dotyczącego wyniku weryfikacji.

Aby przygotować dostawcę tokenów integralności, wykonaj te czynności:

  1. Utwórz StandardIntegrityManager, jak pokazano w przykładach poniżej.
  2. Utwórz obiekt PrepareIntegrityTokenRequest, podając numer projektu Google Cloud za pomocą metody setCloudProjectNumber().
  3. Użyj menedżera, aby wywołać prepareIntegrityToken(), 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();
}

Unreal Engine

// .h
void MyClass::OnPrepareIntegrityTokenCompleted(
  EStandardIntegrityErrorCode ErrorCode,
  UStandardIntegrityTokenProvider* Provider)
{
  // Check the resulting error code.
  if (ErrorCode == EStandardIntegrityErrorCode::StandardIntegrity_NO_ERROR)
  {
    // ...
  }
}

// .cpp
void MyClass::PrepareIntegrityToken()
{
  int64 CloudProjectNumber = ...

  // Create the Integrity Token Request.
  FPrepareIntegrityTokenRequest Request = { CloudProjectNumber };

  // Create a delegate to bind the callback function.
  FPrepareIntegrityOperationCompletedDelegate Delegate;

  // Bind the completion handler (OnPrepareIntegrityTokenCompleted) to the delegate.
  Delegate.BindDynamic(this, &MyClass::OnPrepareIntegrityTokenCompleted);

  // Initiate the prepare integrity token operation, passing the delegate to handle the result.
  GetGameInstance()
    ->GetSubsystem<UStandardIntegrityManager>()
    ->PrepareIntegrityToken(Request, Delegate);
}

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 nieuprawnioną modyfikacją (zalecane)

Gdy sprawdzasz działanie użytkownika w aplikacji za pomocą interfejsu Play Integrity API, możesz użyć pola requestHash, aby zapobiegać atakom polegającym na manipulowaniu danymi. Na przykład gra może chcieć zgłosić wynik gracza do serwera backendu gry, a Twój serwer chce mieć pewność, że wynik nie został zmodyfikowany przez serwer proxy. Interfejs Play Integrity API zwraca wartość ustawioną w polu requestHash w podpisanej odpowiedzi dotyczącej integralności. 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 Poniższe instrukcje pokazują, jak skutecznie korzystać z pola requestHash:

Gdy poprosisz o ocenę integralności:

  • Obliczanie skrótu wszystkich odpowiednich parametrów żądania (np. SHA256 stabilnej serializacji żądania) na podstawie działania użytkownika lub żądania serwera. Wartość ustawiona w polu requestHash może mieć maksymalnie 500 bajtów. W parametrze requestHash uwzględnij wszystkie dane żądania aplikacji, które są kluczowe lub istotne dla sprawdzanego lub chronionego działania. Pole requestHash jest dosłownie uwzględniane w tokenie integralności, więc długie wartości mogą zwiększać rozmiar żądania.
  • Przekaż skrót jako pole requestHash do interfejsu Play Integrity API i uzyskaj token integralności.

Gdy otrzymasz ocenę integralności:

  • Zdekoduj token integralności i wyodrębnij pole requestHash.
  • Oblicz skrót żądania w taki sam sposób jak w aplikacji (np. SHA256 stabilnej serializacji żądania).
  • Porównaj skróty po stronie aplikacji i po stronie serwera. Jeśli nie pasują, żądanie nie jest wiarygodne.

Wysyłanie prośby o ocenę integralności (na żądanie)

Po przygotowaniu dostawcy tokenów integralności możesz zacząć wysyłać do Google Play prośby o oceny integralności. Aby to zrobić, wykonaj te czynności:

  1. Uzyskaj StandardIntegrityTokenProvider.
  2. Utwórz StandardIntegrityTokenRequest, podając hash żą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 wartość 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();
}

Unreal Engine

// .h
void MyClass::OnRequestIntegrityTokenCompleted(
  EStandardIntegrityErrorCode ErrorCode,
  UStandardIntegrityToken* Response)
{
  // Check the resulting error code.
  if (ErrorCode == EStandardIntegrityErrorCode::StandardIntegrity_NO_ERROR)
  {
    // Get the token.
    FString Token = Response->Token;
  }
}

// .cpp
void MyClass::RequestIntegrityToken()
{
  UStandardIntegrityTokenProvider* Provider = ...

  // Prepare the UStandardIntegrityTokenProvider.

  // Request integrity token by providing a user action request hash. Can be called
  // several times for different user actions.
  FString RequestHash = ...;
  FStandardIntegrityTokenRequest Request = { RequestHash };

  // Create a delegate to bind the callback function.
  FStandardIntegrityOperationCompletedDelegate Delegate;

  // Bind the completion handler (OnRequestIntegrityTokenCompleted) to the delegate.
  Delegate.BindDynamic(this, &MyClass::OnRequestIntegrityTokenCompleted);

  // Initiate the standard integrity token request, passing the delegate to handle the result.
  Provider->Request(Request, Delegate);
}

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

Jeśli aplikacja używa tego samego dostawcy tokenów zbyt długo, może on wygasnąć, co spowoduje wystąpienie błędu INTEGRITY_TOKEN_PROVIDER_INVALID przy następnym żądaniu tokena. Ten błąd należy obsłużyć, prosząc o nowego dostawcę.

Odszyfrowywanie i weryfikowanie oceny integralności

Po wysłaniu żądania oceny integralności interfejs Play Integrity API dostarcza 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 danych logowania 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. Odczytaj odpowiedź JSON.

Wynikowy ładunek to token w formacie zwykłego tekstu, który zawiera wyniki weryfikacji integralności.

Automatyczna ochrona przed powtórzeniem

Aby ograniczyć ataki polegające na ponownym odtworzeniu, Google Play automatycznie zapobiega wielokrotnemu używaniu tokenów integralności. Wielokrotne próby odszyfrowania tego samego tokena spowodują wyczyszczenie wyników w ten sposób:

  • Wynik rozpoznawania urządzenia będzie pusty.
  • Ocena rozpoznawania aplikacji i ocena licencjonowania aplikacji zostaną ustawione na wartość UNEVALUATED.
  • Wszystkie opcjonalne wyniki, które są włączone w Konsoli Play, będą miały wartość UNEVALUATED (lub pusty wynik, jeśli jest to wynik wielowartościowy).

Rozwiązywanie problemów z werdyktem za pomocą komunikatu w Google Play (opcjonalnie)

Gdy serwer otrzyma ocenę integralności, może zdecydować, jak postępować dalej. Jeśli wynik wskazuje na problem, np. brak licencji aplikacji, jej zmodyfikowanie lub naruszenie bezpieczeństwa urządzenia, możesz dać użytkownikom szansę na samodzielne rozwiązanie problemu.

Interfejs Play Integrity API umożliwia wyświetlanie okna Google Play, które zachęca użytkownika do podjęcia działania, np. pobrania oficjalnej wersji aplikacji z Google Play.

Aby dowiedzieć się, jak wywoływać te okna w aplikacji na podstawie odpowiedzi serwera, przeczytaj artykuł Okna z informacjami o rozwiązaniu problemu.