package com.topjohnwu.magisk.utils; import android.os.Bundle; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.v4.app.FragmentActivity; import android.util.Base64; import com.google.android.gms.common.ConnectionResult; import com.google.android.gms.common.api.GoogleApiClient; import com.google.android.gms.common.api.Status; import com.google.android.gms.safetynet.SafetyNet; import com.topjohnwu.magisk.R; import org.json.JSONException; import org.json.JSONObject; import java.security.SecureRandom; public abstract class SafetyNetHelper implements GoogleApiClient.OnConnectionFailedListener, GoogleApiClient.ConnectionCallbacks { private static boolean isRunning = false; private GoogleApiClient mGoogleApiClient; private Result ret; protected FragmentActivity mActivity; public SafetyNetHelper(FragmentActivity activity) { ret = new Result(); mActivity = activity; } // Entry point to start test public void requestTest() { if (isRunning) return; // Connect Google Service mGoogleApiClient = new GoogleApiClient.Builder(mActivity) .enableAutoManage(mActivity, this) .addApi(SafetyNet.API) .addConnectionCallbacks(this) .build(); mGoogleApiClient.connect(); isRunning = true; } @Override public void onConnectionFailed(@NonNull ConnectionResult result) { Logger.dev("SN: Google API fail"); ret.errmsg = result.getErrorMessage(); handleResults(ret); } @Override public void onConnectionSuspended(int i) { Logger.dev("SN: Google API Suspended"); switch (i) { case CAUSE_NETWORK_LOST: ret.errmsg = mActivity.getString(R.string.safetyNet_network_loss); break; case CAUSE_SERVICE_DISCONNECTED: ret.errmsg = mActivity.getString(R.string.safetyNet_service_disconnected); break; } handleResults(ret); } @Override public void onConnected(@Nullable Bundle bundle) { Logger.dev("SN: Google API Connected"); // Create nonce byte[] nonce = new byte[24]; new SecureRandom().nextBytes(nonce); Logger.dev("SN: Check with nonce: " + Base64.encodeToString(nonce, Base64.DEFAULT)); // Call SafetyNet SafetyNet.SafetyNetApi.attest(mGoogleApiClient, nonce) .setResultCallback(result -> { Status status = result.getStatus(); if (status.isSuccess()) { String json = new String(Base64.decode(result.getJwsResult().split("\\.")[1], Base64.DEFAULT)); Logger.dev("SN: Response: " + json); try { JSONObject decoded = new JSONObject(json); ret.ctsProfile = decoded.getBoolean("ctsProfileMatch"); ret.basicIntegrity = decoded.getBoolean("basicIntegrity"); ret.failed = false; } catch (JSONException e) { ret.errmsg = mActivity.getString(R.string.safetyNet_res_invalid); } } else { Logger.dev("SN: No response"); ret.errmsg = mActivity.getString(R.string.safetyNet_no_response); } // Disconnect mGoogleApiClient.stopAutoManage(mActivity); mGoogleApiClient.disconnect(); isRunning = false; handleResults(ret); }); } // Callback function to save the results public abstract void handleResults(Result result); public static class Result { public boolean failed = true; public String errmsg; public boolean ctsProfile = false; public boolean basicIntegrity = false; } }