Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Unverified Commit 8eee3635 authored by Marvin W.'s avatar Marvin W. 🐿️
Browse files

SafetyNet: Add basic client library

parent b4d18120
Loading
Loading
Loading
Loading
+35 −0
Original line number Diff line number Diff line
/*
 * SPDX-FileCopyrightText: 2022 microG Project Team
 * SPDX-License-Identifier: Apache-2.0
 * Notice: Portions of this file are reproduced from work created and shared by Google and used
 *         according to terms described in the Creative Commons 4.0 Attribution License.
 *         See https://developers.google.com/readme/policies for details.
 */

package com.google.android.gms.common.api;

import org.microg.gms.common.PublicApi;

/**
 * Represents the successful result of invoking an API method in Google Play services using a subclass of GoogleApi.
 * Wraps a instance of a {@link Result}.
 */
@PublicApi
public class Response<T extends Result> {
    private T result;

    public Response() {
    }

    protected Response(T result) {
        this.result = result;
    }

    public T getResult() {
        return result;
    }

    public void setResult(T result) {
        this.result = result;
    }
}
+11 −0
Original line number Diff line number Diff line
/*
 * SPDX-FileCopyrightText: 2022 microG Project Team
 * SPDX-License-Identifier: CC-BY-4.0
 * Notice: Portions of this file are reproduced from work created and shared by Google and used
 *         according to terms described in the Creative Commons 4.0 Attribution License.
 *         See https://developers.google.com/readme/policies for details.
 */
/**
 * Contains utility classes for Google Play services.
 */
package com.google.android.gms.common;
+3 −0
Original line number Diff line number Diff line
@@ -53,6 +53,9 @@ dependencies {
    implementation project(':play-services-wearable')
    implementation "org.microg:wearable:$wearableVersion"

    implementation project(':play-services-safetynet')
    implementation project(':play-services-tasks-ktx')

    runtimeOnly "org.microg.nlp:geocode-v1:$nlpVersion"
    runtimeOnly "org.microg.nlp:location-v2:$nlpVersion"
    runtimeOnly "org.microg.nlp:location-v3:$nlpVersion"
+70 −66
Original line number Diff line number Diff line
@@ -16,12 +16,11 @@ import androidx.preference.Preference
import androidx.preference.PreferenceFragmentCompat
import com.google.android.gms.R
import com.google.android.gms.common.api.Status
import com.google.android.gms.safetynet.AttestationData
import com.google.android.gms.safetynet.RecaptchaResultData
import com.google.android.gms.safetynet.SafetyNet
import com.google.android.gms.safetynet.internal.ISafetyNetCallbacks
import org.json.JSONException
import com.google.android.gms.tasks.await
import org.json.JSONObject
import org.microg.gms.safetynet.SafetyNetClientService
import org.microg.gms.safetynet.SafetyNetClientServiceImpl
import kotlin.random.Random

@@ -43,31 +42,34 @@ class SafetyNetPreferencesFragment : PreferenceFragmentCompat() {
            val context = context ?: return@setOnPreferenceClickListener false
            runAttest.setIcon(R.drawable.ic_circle_pending)
            runAttest.setSummary(R.string.pref_test_summary_running)
            val handler = Handler(Looper.myLooper()!!)
            SafetyNetClientServiceImpl(context, context.packageName, lifecycle).attestWithApiKey(object : ISafetyNetCallbacks.Default() {
                override fun onAttestationData(status: Status?, attestationData: AttestationData?) {
                    handler.post {
                        if (status?.isSuccess == true) {
                            if (attestationData?.jwsResult == null) {
            lifecycleScope.launchWhenResumed {
                val response = SafetyNet.getClient(requireActivity())
                    .attest(Random.nextBytes(32), "AIzaSyCcJO6IZiA5Or_AXw3LFdaTCmpnfL4pJ-Q").await()
                if (response.result.status?.isSuccess == true) {
                    if (response.jwsResult == null) {
                        runAttest.setIcon(R.drawable.ic_circle_warn)
                        runAttest.summary = context.getString(R.string.pref_test_summary_failed, "No result")
                    } else {
                        val (_, payload, _) = try {
                                    attestationData.jwsResult.split(".")
                            response.jwsResult.split(".")
                        } catch (e: Exception) {
                            runAttest.setIcon(R.drawable.ic_circle_error)
                            runAttest.summary = context.getString(R.string.pref_test_summary_failed, "Invalid JWS")
                                    return@post
                            return@launchWhenResumed
                        }
                        val (basicIntegrity, ctsProfileMatch, advice) = try {
                            JSONObject(Base64.decode(payload, Base64.URL_SAFE).decodeToString()).let {
                                        Triple(it.optBoolean("basicIntegrity", false), it.optBoolean("ctsProfileMatch", false), it.optString("advice", ""))
                                Triple(
                                    it.optBoolean("basicIntegrity", false),
                                    it.optBoolean("ctsProfileMatch", false),
                                    it.optString("advice", "")
                                )
                            }
                        } catch (e: Exception) {
                            Log.w(TAG, e)
                            runAttest.setIcon(R.drawable.ic_circle_error)
                            runAttest.summary = context.getString(R.string.pref_test_summary_failed, "Invalid JSON")
                                    return@post
                            return@launchWhenResumed
                        }
                        val adviceText = if (advice == "") "" else "\n" + advice.split(",").map {
                            when (it) {
@@ -83,41 +85,43 @@ class SafetyNetPreferencesFragment : PreferenceFragmentCompat() {
                            }
                            basicIntegrity -> {
                                runAttest.setIcon(R.drawable.ic_circle_warn)
                                        runAttest.summary = context.getString(R.string.pref_test_summary_warn, "CTS profile does not match$adviceText")
                                runAttest.summary = context.getString(
                                    R.string.pref_test_summary_warn,
                                    "CTS profile does not match$adviceText"
                                )
                            }
                            else -> {
                                runAttest.setIcon(R.drawable.ic_circle_error)
                                        runAttest.summary = context.getString(R.string.pref_test_summary_failed, "integrity check failed$adviceText")
                                runAttest.summary = context.getString(
                                    R.string.pref_test_summary_failed,
                                    "integrity check failed$adviceText"
                                )
                            }
                        }
                    }
                } else {
                    runAttest.setIcon(R.drawable.ic_circle_error)
                            runAttest.summary = context.getString(R.string.pref_test_summary_failed, status?.statusMessage)
                    runAttest.summary =
                        context.getString(R.string.pref_test_summary_failed, response.result.status?.statusMessage)
                }
            }
                }
            }, Random.nextBytes(32), "AIzaSyCcJO6IZiA5Or_AXw3LFdaTCmpnfL4pJ-Q")
            true
        }
        runReCaptcha.setOnPreferenceClickListener {
            val context = context ?: return@setOnPreferenceClickListener false
            runReCaptcha.setIcon(R.drawable.ic_circle_pending)
            runReCaptcha.setSummary(R.string.pref_test_summary_running)
            val handler = Handler(Looper.myLooper()!!)
            SafetyNetClientServiceImpl(context, context.packageName, lifecycle).verifyWithRecaptcha(object : ISafetyNetCallbacks.Default() {
                override fun onRecaptchaResult(status: Status?, recaptchaResultData: RecaptchaResultData?) {
                    handler.post {
                        if (status?.isSuccess == true) {
            lifecycleScope.launchWhenResumed {
                val response = SafetyNet.getClient(requireActivity()).verifyWithRecaptcha("6Lc4TzgeAAAAAJnW7Jbo6UtQ0xGuTKjHAeyhINuq").await()
                if (response.result.status?.isSuccess == true) {
                    runReCaptcha.setIcon(R.drawable.ic_circle_check)
                    runReCaptcha.setSummary(R.string.pref_test_summary_passed)
                } else {
                    runReCaptcha.setIcon(R.drawable.ic_circle_error)
                            runReCaptcha.summary = context.getString(R.string.pref_test_summary_failed, status?.statusMessage)
                        }
                    runReCaptcha.summary =
                        context.getString(R.string.pref_test_summary_failed, response.result.status?.statusMessage)
                }
            }
            }, "6Lc4TzgeAAAAAJnW7Jbo6UtQ0xGuTKjHAeyhINuq")
            true
        }
    }
+34 −0
Original line number Diff line number Diff line
@@ -14,12 +14,38 @@ import com.google.android.gms.common.api.CommonStatusCodes;
 * Status codes for the SafetyNet API.
 */
public class SafetyNetStatusCodes extends CommonStatusCodes {
    /**
     * None of the input threat types to {@code lookupUri(String, String, int...)} are supported.
     */
    public static final int SAFE_BROWSING_UNSUPPORTED_THREAT_TYPES = 12000;
    /**
     * The API key required for calling {@code lookupUri(String, String, int...)} is missing in the manifest.
     * <p>
     * A meta-data name-value pair in the app manifest with the name "com.google.android.safetynet.API_KEY" and a value
     * consisting of the API key from the Google Developers Console is not present.
     */
    public static final int SAFE_BROWSING_MISSING_API_KEY = 12001;
    /**
     * An internal error occurred causing the call to {@code lookupUri(String, String, int...)} to be unavailable.
     */
    public static final int SAFE_BROWSING_API_NOT_AVAILABLE = 12002;
    /**
     * Verify Apps is not supported on this device.
     */
    public static final int VERIFY_APPS_NOT_AVAILABLE = 12003;
    /**
     * An internal error occurred while using the Verify Apps API.
     */
    public static final int VERIFY_APPS_INTERNAL_ERROR = 12004;
    /**
     * Cannot list potentially harmful apps because Verify Apps is not enabled.
     * <p>
     * The developer may call {@code enableVerifyApps()} to request the user turn on Verify Apps.
     */
    public static final int VERIFY_APPS_NOT_ENABLED = 12005;
    /**
     * User device SDK version is not supported.
     */
    public static final int UNSUPPORTED_SDK_VERSION = 12006;
    /**
     * Cannot start the reCAPTCHA service because site key parameter is not valid.
@@ -27,11 +53,19 @@ public class SafetyNetStatusCodes extends CommonStatusCodes {
    public static final int RECAPTCHA_INVALID_SITEKEY = 12007;
    /**
     * Cannot start the reCAPTCHA service because type of site key is not valid.
     * <p>
     * Please register new site key with the key type set to "reCAPTCHA Android"
     */
    public static final int RECAPTCHA_INVALID_KEYTYPE = 12008;
    /**
     * {@code lookupUri(String, String, int...)} called without first calling {@code initSafeBrowsing()}.
     */
    public static final int SAFE_BROWSING_API_NOT_INITIALIZED = 12009;
    /**
     * Cannot start the reCAPTCHA service because calling package name is not matched with site key.
     * <p>
     * Please add the new package name to your site key via reCAPTCHA Admin Console or choose to disable the package
     * name validation for your key.
     */
    public static final int RECAPTCHA_INVALID_PACKAGE_NAME = 12013;
}
Loading