2

I've successfully implemented Google SafetyNet API, even have a successful response. Problem is that the JWSResult from AttestationResponse is a hashed string, whereas my expectation was to get a JSON in response.

May I ask where do I need to first look for problems?

Here is the code where attest() is called:

    fun callSafetyNetAttentationApi(context: Activity, callback: SafetyNetCallback) {

    if (GoogleApiAvailability.getInstance().isGooglePlayServicesAvailable(context) == ConnectionResult.SUCCESS) {

        val nonce: ByteArray? = getRequestNonce("Safety Net Sample: " + System.currentTimeMillis())
        val client = SafetyNet.getClient(context)

        nonce?.let {
            val task: Task<AttestationResponse> = client.attest(it, BuildConfig.SAFETY_NET_KEY)

            task.addOnSuccessListener { response -> safetyNetSuccess(response, callback) }
                    .addOnFailureListener { callback.isDeviceTrusted(false) }

        } ?: kotlin.run {
            callback.isDeviceTrusted(false)
        }

    } else {
        MaterialDialog.Builder(context)
                .title("The app cannot be used")
                .content("Please update Google Play Services and try again.")
                .cancelable(false)
                .positiveText("Dismiss")
                .onPositive { dialog, which -> context.finish() }
                .show()
    }
}
azizbekian
  • 60,783
  • 13
  • 169
  • 249
Lendl Leyba
  • 2,287
  • 3
  • 34
  • 49

2 Answers2

1

This is a typical JSON response that you'll receive after performing safetyNetClient.attest(nonce, apiKey):

{
   "jwsResult": "foo.bar.zar",
   "uid": "11426643",
   "authCode": "H3o28i\/ViJUPRAW\/q4IUe1AMAbD-2jYp82os9v",
   "app": 1,
   "androidId": "vece15a43449afa9"
}

Here foo.bar.zar is a base64 encoded string, something like aisnfaksdf.8439hundf.ghbadsjn, where each part corresponds to:

<Base64 encoded header>.<Base64 encoded JSON data>.<Base64 encoded signature>

You need to take the bar and Base64 decode that in order to get the SafetyNet result JSON:

    private fun extractJwsData(jws: String?): ByteArray? {
        val parts = jws?.split("[.]".toRegex())?.dropLastWhile { it.isEmpty() }?.toTypedArray()
        if (parts?.size != 3) {
            System.err.println("Failure: Illegal JWS signature format. The JWS consists of "
                    + parts?.size + " parts instead of 3.")
            return null
        }
        return Base64.decode(parts[1], Base64.DEFAULT)
    }

Then construct java object using the JSON library you like, e.g. GSON:

val model = Gson().fromJson(extractJwsData(jws).toString(), SafetyNetApiModel::class.java)

Where SafetyNetApiModel is:

class SafetyNetApiModel {
    @SerializedName("nonce")
    var nonce: String? = null

    @SerializedName("timestampMs")
    var timestampMs: Long? = null

    @SerializedName("apkPackageName")
    var apkPackageName: String? = null

    @SerializedName("apkCertificateDigestSha256")
    var apkCertificateDigestSha256: List<String>? = null

    @SerializedName("apkDigestSha256")
    var apkDigestSha256: String? = null

    @SerializedName("ctsProfileMatch")
    var ctsProfileMatch: Boolean? = null

    @SerializedName("basicIntegrity")
    var basicIntegrity: Boolean? = null
}

Have a look at this for reference.

azizbekian
  • 60,783
  • 13
  • 169
  • 249
  • I didn't follow your code because you can't convert byte array to json. So i turned it into string before converting to json, but this is what i got "[B@efc8722" – Lendl Leyba Jun 25 '20 at 11:24
0

JWS response is always the sign result. After getting the JWS response, you have to verify it from the server-side code with the nonce then the server will be verified the request and if it is a valid request then it returns JSON response like this

Check out the link send JWS response to the server for verification

For sample app:

private OnSuccessListener<SafetyNetApi.AttestationResponse> mSuccessListener =
        new OnSuccessListener<SafetyNetApi.AttestationResponse>() {
            @Override
            public void onSuccess(SafetyNetApi.AttestationResponse attestationResponse) {
                /*
                 Successfully communicated with SafetyNet API.
                 Use result.getJwsResult() to get the signed result data. See the server
                 component of this sample for details on how to verify and parse this result.
                 */
                mResult = attestationResponse.getJwsResult();
                Log.d(TAG, "Success! SafetyNet result:\n" + mResult + "\n");

                    /*
                     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!
                    */
            }
        };

Read the comment inside the success response, this code is from GitHub SafetyNet sample app

mdroid
  • 474
  • 3
  • 15
  • it may not be a 100% solution, but i'm trying to apply this only on the app level. please see my comment on the first answer. – Lendl Leyba Jun 29 '20 at 03:10
  • You don't need to convert into Base64, just send the JWS response hash string to the server for verification, It always works, I have checked that code of client and server-side, It's working for me – mdroid Jun 29 '20 at 06:05
  • 1
    but my backend hasn't implemented safetynet server side yet. – Lendl Leyba Jun 29 '20 at 06:15
  • 1
    So how to test the device integrity, If you are writing the server code into your client app then what is the use of it. You are generating nonce in the client app and verify the response is also in the client app. How you will check if the device is rooted and device integrity, any hacker or attacker will bypass it and run your app into the rooted device. – mdroid Jun 29 '20 at 06:33
  • Hey @klutch, Have you parse the JWS response into JSON without calling the API from server? let me know if it is done then it will help for me – mdroid Jun 30 '20 at 10:48
  • Hi @klutch, Find more details of use SafetyNet attestation API https://koz.io/inside-safetynet/ – mdroid Jul 01 '20 at 12:18