I'm trying to detect rooted device on my app before my login process. Regarding to this answer , I decided to use SafetyNet. I edited this code like this:
public class RootedDeviceCheckWithSafetyNetAttestation {
public static AtomicInteger sendSafetyNetRequest(Activity context) {
AtomicInteger rootCheck = new AtomicInteger();
if(GoogleApiAvailability.getInstance().isGooglePlayServicesAvailable(context) == ConnectionResult.SUCCESS) {
Log.e(TAG, "The SafetyNet Attestation API is available");
// TODO(developer): Change the nonce generation to include your own, used once value,
// ideally from your remote server.
String nonceData = "Safety Net Sample: " + System.currentTimeMillis();
ByteArrayOutputStream byteStream = new ByteArrayOutputStream();
Random mRandom = new SecureRandom();
byte[] bytes = new byte[24];
mRandom.nextBytes(bytes);
try {
byteStream.write(bytes);
byteStream.write(nonceData.getBytes());
} catch (IOException e) {
e.printStackTrace();
}
byte[] nonce = byteStream.toByteArray();
SafetyNetClient client = SafetyNet.getClient(context);
Task<SafetyNetApi.AttestationResponse> task = client.attest(nonce, MY-API-KEY);
task.addOnSuccessListener(context, attestationResponse -> {
/*
TODO(developer): Forward this result to your server together with
the nonce for verification.
You can also parse the JwsResult locally to confirm that the API
returned a response by checking for an 'error' field first and before
retrying the request with an exponential backoff.
NOTE: Do NOT rely on a local, client-side only check for security, you
must verify the response on a remote server!
*/
String jwsResult = attestationResponse.getJwsResult();
Log.e(TAG, "Success! SafetyNet result:\n" + jwsResult + "\n");
if (jwsResult == null) {
Log.e(TAG, "jwsResult Null");
}
final String[] jwtParts = jwsResult.split("\\.");
if (jwtParts.length == 3) {
String decodedPayload = new String(Base64.decode(jwtParts[1], Base64.DEFAULT));
try {
JSONObject jsonObject = new JSONObject(decodedPayload);
//Toast.makeText(context, "ctsProfileMatch:" + jsonObject.getString("ctsProfileMatch")+","+"basicIntegrity:" + jsonObject.getString("basicIntegrity"), Toast.LENGTH_SHORT).show();
if((jsonObject.getString("ctsProfileMatch").equals("false")) && (jsonObject.getString("basicIntegrity").equals("false"))){
rootCheck.set(4);
}
else{
rootCheck.set(1);
}
} catch (JSONException e) {
e.printStackTrace();
rootCheck.set(2);
}
//Toast.makeText(context, decodedPayload, Toast.LENGTH_SHORT).show();
//Log.e(TAG, "decodedPayload : " + decodedPayload);
}
});
task.addOnFailureListener(context, e -> {
// An error occurred while communicating with the service.
String mResult = null;
rootCheck.set(5);
if (e instanceof ApiException) {
// An error with the Google Play Services API contains some additional details.
ApiException apiException = (ApiException) e;
Log.e(TAG, "Error: " +
CommonStatusCodes.getStatusCodeString(apiException.getStatusCode()) + ": " +
apiException.getStatusMessage());
Toast.makeText(context, "Error: " +
CommonStatusCodes.getStatusCodeString(apiException.getStatusCode()) + ": " +
apiException.getStatusMessage(), Toast.LENGTH_LONG).show();
} else {
// A different, unknown type of error occurred.
Log.e(TAG, "ERROR! " + e.getMessage());
Toast.makeText(context, "ERROR! " + e.getMessage(), Toast.LENGTH_LONG).show();
}
});
} else {
Log.e(TAG, "Prompt user to update Google Play services.");
rootCheck.set(3);
}
return rootCheck;
}
}
When I try to on emulator or rooted device, I can see that my rootCheck
variable becomes 4
as I written in code.
The problem is; in real phones, at first login, my code can detect rooted device too. But if I close the app and kill from background and re-open again, I see that my code gives this toast Error: CANCELLED: null
so I see that rootCheck
variable 5
which means that jumps to task.addOnFailureListener
area in if (e instanceof ApiException)
. This problem causes on rooted and normal device, doesn't matter.
What is the problem? Can you help me ?