Monday, July 18, 2022

Android Device Attestation Client Side and Server Side using Play Integrity



Attestation Posts:


 


In the previous post, we've presented mobile device attestation using Google's SafetyNet. However, in recently Google announced disontinue of SafetyNet. Hence we've updated our client side and server side to use PlayIntegrity instead.


Unlike SafetyNet, PlayIntegrity documentation site is extremely poor, and the migrate was frustrating. Some of the insights about how PlayIntegrity were revealed in this stackoverflow post (but it does the attestation on the mobile device only). Other insights were only a matter of trying various APIs with zero documentation.

I sum the steps done below, and hope that in the future, the documentation will get better.


Client Side

First, add dependencies:


dependencies {
    implementation 'com.google.android.play:integrity:1.0.1'                  
    implementation 'com.google.apis:google-api-services-playintegrity:v1-rev20220211-1.32.1'
    implementation 'com.google.api-client:google-api-client-jackson2:1.20.0'
    implementation 'com.google.auth:google-auth-library-credentials:1.7.0'
    implementation 'com.google.auth:google-auth-library-oauth2-http:1.7.0'
}


Next implement token request on the client side, and send it to your backend server.

Project ID can be located as explained here.




import android.content.Context;

import com.google.android.gms.tasks.Task;
import com.google.android.play.core.integrity.IntegrityManager;
import com.google.android.play.core.integrity.IntegrityManagerFactory;
import com.google.android.play.core.integrity.IntegrityTokenRequest;
import com.google.android.play.core.integrity.IntegrityTokenResponse;

public class Attestation {

public void start(String nonce) {
IntegrityManager integrityManager = IntegrityManagerFactory.create(context);

Task<IntegrityTokenResponse> integrityTokenResponse = integrityManager
.requestIntegrityToken(IntegrityTokenRequest
.builder()
.setNonce(nonce)
.setCloudProjectNumber(YOUR_GCP_PROJECT_NUMBER_HERE)
.build());

integrityTokenResponse.addOnSuccessListener(response -> {
String token = response.token();
ExecutorService executor = Executors.newSingleThreadExecutor();
executor.execute(() -> {
// send the token to your backend server
});
});
}


}



Server Side

On the server side, we get the token sent from the client, and ask Google's verdict for the token.

In this case, we assume the device is valid in case the verdict includes MEETS_DEVICE_INTEGRITY.

To enable access to Google services, we create an account credentials json file(see instructions in the stackoverflow post), and send it in the GoogleCredentialsPath variable.


package attestation

import (
"context"
"fmt"
"google.golang.org/api/option"
"google.golang.org/api/playintegrity/v1"
)

func Execute() error {
request := AttestationRequest{}
err := GetBody(&request)
if err != nil {
return fmt.Errorf("get body failed: %v", err)
}

ctx := context.Background()
playIntegrityService, err := playintegrity.NewService(ctx, option.WithCredentialsFile(core.Config.GoogleCredentialsPath))
if err != nil {
return fmt.Errorf("create play integrity service failed: %v", err)
}

tokenRequest := playintegrity.DecodeIntegrityTokenRequest{
IntegrityToken: request.Token,
ForceSendFields: nil,
NullFields: nil,
}

tokenCall := playIntegrityService.V1.DecodeIntegrityToken("your.client.java.package.prefix.com", &tokenRequest)
response, err := tokenCall.Do()
if err != nil {
return fmt.Errorf("token call failed: %v", err)
}

if response == nil {
return fmt.Errorf("response is null")
}

payload := response.TokenPayloadExternal
if payload == nil {
return fmt.Errorf("payload is null")
}

integrity := payload.DeviceIntegrity
if integrity == nil {
return fmt.Errorf("integrity is null")
}

for _, verdict := range integrity.DeviceRecognitionVerdict {
if verdict == "MEETS_DEVICE_INTEGRITY" {
return nil
}
}

return fmt.Errorf("verdict is %v", integrity.DeviceRecognitionVerdict)
}



Final Note


In this post we have used Google PlayIntegrity to validate a mobile device.

We presented the steps for client side and server side implementation.

Make sure to check quotas for the allowed attestation requests before moving to production environment.





No comments:

Post a Comment