Repo Created
This commit is contained in:
parent
eb305e2886
commit
a8c22c65db
4784 changed files with 329907 additions and 2 deletions
41
play-services-safetynet/build.gradle
Normal file
41
play-services-safetynet/build.gradle
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2022 microG Project Team
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
apply plugin: 'com.android.library'
|
||||
apply plugin: 'maven-publish'
|
||||
apply plugin: 'signing'
|
||||
|
||||
android {
|
||||
namespace "com.google.android.gms.safetynet"
|
||||
|
||||
compileSdkVersion androidCompileSdk
|
||||
buildToolsVersion "$androidBuildVersionTools"
|
||||
|
||||
buildFeatures {
|
||||
aidl = true
|
||||
}
|
||||
|
||||
defaultConfig {
|
||||
versionName version
|
||||
minSdkVersion androidMinSdk
|
||||
targetSdkVersion androidTargetSdk
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility = 1.8
|
||||
targetCompatibility = 1.8
|
||||
}
|
||||
}
|
||||
|
||||
apply from: '../gradle/publish-android.gradle'
|
||||
|
||||
description = 'microG implementation of play-services-safetynet'
|
||||
|
||||
dependencies {
|
||||
// Dependencies from play-services-safetynet:18.0.1
|
||||
api project(':play-services-base')
|
||||
api project(':play-services-basement')
|
||||
api project(':play-services-tasks')
|
||||
}
|
||||
72
play-services-safetynet/core/build.gradle
Normal file
72
play-services-safetynet/core/build.gradle
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2021 microG Project Team
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
apply plugin: 'com.android.library'
|
||||
apply plugin: 'com.squareup.wire'
|
||||
apply plugin: 'kotlin-android'
|
||||
apply plugin: 'maven-publish'
|
||||
apply plugin: 'signing'
|
||||
|
||||
dependencies {
|
||||
api project(':play-services-safetynet')
|
||||
|
||||
implementation project(':play-services-base-core')
|
||||
implementation project(':play-services-droidguard')
|
||||
implementation project(':play-services-droidguard-core')
|
||||
implementation project(':play-services-tasks-ktx')
|
||||
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlinVersion"
|
||||
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutineVersion"
|
||||
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutineVersion"
|
||||
|
||||
implementation "androidx.appcompat:appcompat:$appcompatVersion"
|
||||
implementation "androidx.core:core-ktx:$coreVersion"
|
||||
implementation "androidx.lifecycle:lifecycle-runtime-ktx:$lifecycleVersion"
|
||||
implementation "androidx.lifecycle:lifecycle-service:$lifecycleVersion"
|
||||
implementation "androidx.webkit:webkit:$webkitVersion"
|
||||
|
||||
implementation "com.android.volley:volley:$volleyVersion"
|
||||
implementation "com.squareup.wire:wire-runtime:$wireVersion"
|
||||
}
|
||||
|
||||
wire {
|
||||
kotlin {
|
||||
javaInterop = true
|
||||
}
|
||||
}
|
||||
|
||||
android {
|
||||
namespace "org.microg.gms.safetynet.core"
|
||||
|
||||
compileSdkVersion androidCompileSdk
|
||||
buildToolsVersion "$androidBuildVersionTools"
|
||||
|
||||
defaultConfig {
|
||||
versionName version
|
||||
minSdkVersion androidMinSdk
|
||||
targetSdkVersion androidTargetSdk
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
main.java.srcDirs += 'src/main/kotlin'
|
||||
}
|
||||
|
||||
lintOptions {
|
||||
disable 'MissingTranslation'
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility = 1.8
|
||||
targetCompatibility = 1.8
|
||||
}
|
||||
|
||||
kotlinOptions {
|
||||
jvmTarget = 1.8
|
||||
}
|
||||
}
|
||||
|
||||
apply from: '../../gradle/publish-android.gradle'
|
||||
|
||||
description = 'microG service implementation for play-services-safetynet'
|
||||
29
play-services-safetynet/core/src/main/AndroidManifest.xml
Normal file
29
play-services-safetynet/core/src/main/AndroidManifest.xml
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
~ SPDX-FileCopyrightText: 2021 microG Project Team
|
||||
~ SPDX-License-Identifier: Apache-2.0
|
||||
-->
|
||||
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<application>
|
||||
<service android:name="org.microg.gms.safetynet.SafetyNetClientService">
|
||||
<intent-filter>
|
||||
<action android:name="com.google.android.gms.safetynet.service.START" />
|
||||
</intent-filter>
|
||||
</service>
|
||||
<activity
|
||||
android:name="org.microg.gms.safetynet.ReCaptchaActivity"
|
||||
android:autoRemoveFromRecents="true"
|
||||
android:icon="@drawable/ic_recaptcha"
|
||||
android:process=":ui"
|
||||
android:exported="false"
|
||||
android:theme="@style/Theme.AppCompat.Light.Dialog.NoActionBar">
|
||||
<intent-filter>
|
||||
<action android:name="org.microg.gms.safetynet.RECAPTCHA_ACTIVITY" />
|
||||
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
</application>
|
||||
</manifest>
|
||||
|
|
@ -0,0 +1,206 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2021 microG Project Team
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package org.microg.gms.safetynet;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.Context;
|
||||
import android.content.pm.PackageInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.Signature;
|
||||
import android.util.Base64;
|
||||
import android.util.Log;
|
||||
|
||||
import org.microg.gms.common.Constants;
|
||||
import org.microg.gms.common.PackageUtils;
|
||||
import org.microg.gms.common.Utils;
|
||||
import org.microg.gms.profile.Build;
|
||||
import org.microg.gms.profile.ProfileManager;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.URL;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.CharBuffer;
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.charset.CharsetDecoder;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.zip.GZIPInputStream;
|
||||
|
||||
import okio.ByteString;
|
||||
|
||||
public class Attestation {
|
||||
private static final String TAG = "GmsSafetyNetAttest";
|
||||
|
||||
private Context context;
|
||||
private String packageName;
|
||||
private byte[] payload;
|
||||
private String droidGuardResult;
|
||||
|
||||
public Attestation(Context context, String packageName) {
|
||||
this.context = context;
|
||||
this.packageName = packageName;
|
||||
}
|
||||
|
||||
public void setPayload(byte[] payload) {
|
||||
this.payload = payload;
|
||||
}
|
||||
|
||||
public SafetyNetData buildPayload(byte[] nonce) {
|
||||
this.droidGuardResult = null;
|
||||
SafetyNetData payload = new SafetyNetData.Builder()
|
||||
.nonce(ByteString.of(nonce))
|
||||
.currentTimeMs(System.currentTimeMillis())
|
||||
.packageName(packageName)
|
||||
.fileDigest(getPackageFileDigest())
|
||||
.signatureDigest(getPackageSignatures())
|
||||
.gmsVersionCode(Constants.GMS_VERSION_CODE)
|
||||
//.googleCn(false)
|
||||
.seLinuxState(new SELinuxState.Builder().enabled(true).supported(true).build())
|
||||
.suCandidates(Collections.<FileState>emptyList())
|
||||
.build();
|
||||
Log.d(TAG, "Payload: "+payload.toString());
|
||||
this.payload = payload.encode();
|
||||
return payload;
|
||||
}
|
||||
|
||||
public byte[] getPayload() {
|
||||
return payload;
|
||||
}
|
||||
|
||||
public String getPayloadHashBase64() {
|
||||
try {
|
||||
MessageDigest digest = getSha256Digest();
|
||||
return Base64.encodeToString(digest.digest(payload), Base64.NO_WRAP);
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
Log.w(TAG, e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static MessageDigest getSha256Digest() throws NoSuchAlgorithmException {
|
||||
return MessageDigest.getInstance("SHA-256");
|
||||
}
|
||||
|
||||
public void setDroidGuardResult(String droidGuardResult) {
|
||||
this.droidGuardResult = droidGuardResult;
|
||||
}
|
||||
|
||||
private ByteString getPackageFileDigest() {
|
||||
try {
|
||||
return ByteString.of(getPackageFileDigest(context, packageName));
|
||||
} catch (Exception e) {
|
||||
Log.w(TAG, e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static byte[] getPackageFileDigest(Context context, String packageName) throws Exception {
|
||||
FileInputStream is = new FileInputStream(new File(context.getPackageManager().getApplicationInfo(packageName, 0).sourceDir));
|
||||
MessageDigest digest = getSha256Digest();
|
||||
byte[] data = new byte[4096];
|
||||
while (true) {
|
||||
int read = is.read(data);
|
||||
if (read < 0) break;
|
||||
digest.update(data, 0, read);
|
||||
}
|
||||
is.close();
|
||||
return digest.digest();
|
||||
}
|
||||
|
||||
@SuppressLint("PackageManagerGetSignatures")
|
||||
private List<ByteString> getPackageSignatures() {
|
||||
try {
|
||||
ArrayList<ByteString> res = new ArrayList<>();
|
||||
for (byte[] bytes : getPackageSignatures(context, packageName)) {
|
||||
res.add(ByteString.of(bytes));
|
||||
}
|
||||
return res;
|
||||
} catch (Exception e) {
|
||||
Log.w(TAG, e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static byte[][] getPackageSignatures(Context context, String packageName) throws Exception {
|
||||
PackageInfo pi = context.getPackageManager().getPackageInfo(packageName, PackageManager.GET_SIGNATURES);
|
||||
ArrayList<byte[]> res = new ArrayList<>();
|
||||
MessageDigest digest = getSha256Digest();
|
||||
for (Signature signature : pi.signatures) {
|
||||
res.add(digest.digest(signature.toByteArray()));
|
||||
}
|
||||
return res.toArray(new byte[][]{});
|
||||
}
|
||||
|
||||
public String attest(String apiKey) throws IOException {
|
||||
if (payload == null) {
|
||||
throw new IllegalStateException("missing payload");
|
||||
}
|
||||
return attest(new AttestRequest.Builder().safetyNetData(ByteString.of(payload)).droidGuardResult(droidGuardResult).build(), apiKey).result;
|
||||
}
|
||||
|
||||
private AttestResponse attest(AttestRequest request, String apiKey) throws IOException {
|
||||
ProfileManager.ensureInitialized(context);
|
||||
String requestUrl = "https://www.googleapis.com/androidcheck/v1/attestations/attest?alt=PROTO&key=" + apiKey;
|
||||
HttpURLConnection connection = (HttpURLConnection) new URL(requestUrl).openConnection();
|
||||
connection.setRequestMethod("POST");
|
||||
connection.setDoInput(true);
|
||||
connection.setDoOutput(true);
|
||||
connection.setRequestProperty("content-type", "application/x-protobuf");
|
||||
connection.setRequestProperty("Accept-Encoding", "gzip");
|
||||
connection.setRequestProperty("X-Android-Package", packageName);
|
||||
connection.setRequestProperty("X-Android-Cert", PackageUtils.firstSignatureDigest(context, packageName));
|
||||
connection.setRequestProperty("User-Agent", "SafetyNet/" + Constants.GMS_VERSION_CODE + " (" + Build.DEVICE + " " + Build.ID + "); gzip");
|
||||
|
||||
OutputStream os = connection.getOutputStream();
|
||||
os.write(request.encode());
|
||||
os.close();
|
||||
|
||||
if (connection.getResponseCode() != 200) {
|
||||
byte[] bytes = null;
|
||||
String ex = null;
|
||||
try {
|
||||
bytes = Utils.readStreamToEnd(connection.getErrorStream());
|
||||
ex = new String(Utils.readStreamToEnd(new GZIPInputStream(new ByteArrayInputStream(bytes))));
|
||||
} catch (Exception e) {
|
||||
if (bytes != null) {
|
||||
throw new IOException(getBytesAsString(bytes), e);
|
||||
}
|
||||
throw new IOException(connection.getResponseMessage(), e);
|
||||
}
|
||||
throw new IOException(ex);
|
||||
}
|
||||
|
||||
InputStream is = connection.getInputStream();
|
||||
byte[] bytes = Utils.readStreamToEnd(new GZIPInputStream(is));
|
||||
try {
|
||||
return AttestResponse.ADAPTER.decode(bytes);
|
||||
} catch (IOException e) {
|
||||
Log.d(TAG, Base64.encodeToString(bytes, 0));
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private String getBytesAsString(byte[] bytes) {
|
||||
if (bytes == null) return "null";
|
||||
try {
|
||||
CharsetDecoder d = Charset.forName("US-ASCII").newDecoder();
|
||||
CharBuffer r = d.decode(ByteBuffer.wrap(bytes));
|
||||
return r.toString();
|
||||
} catch (Exception e) {
|
||||
return Base64.encodeToString(bytes, Base64.NO_WRAP);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,240 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2021 microG Project Team
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package org.microg.gms.safetynet
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.net.http.SslCertificate
|
||||
import android.os.Build.VERSION.SDK_INT
|
||||
import android.os.Bundle
|
||||
import android.os.ResultReceiver
|
||||
import android.util.Base64
|
||||
import android.util.Log
|
||||
import android.view.View
|
||||
import android.view.Window
|
||||
import android.webkit.*
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.webkit.WebViewClientCompat
|
||||
import com.google.android.gms.droidguard.DroidGuardClient
|
||||
import com.google.android.gms.safetynet.SafetyNetStatusCodes.*
|
||||
import com.google.android.gms.tasks.await
|
||||
import org.microg.gms.profile.Build
|
||||
import org.microg.gms.profile.ProfileManager
|
||||
import org.microg.gms.safetynet.core.R
|
||||
import java.io.ByteArrayInputStream
|
||||
import java.net.URLEncoder
|
||||
import java.security.MessageDigest
|
||||
import kotlin.math.min
|
||||
|
||||
private const val TAG = "GmsReCAPTCHA"
|
||||
|
||||
private fun StringBuilder.appendUrlEncodedParam(key: String, value: String?) = append("&")
|
||||
.append(URLEncoder.encode(key, "UTF-8"))
|
||||
.append("=")
|
||||
.append(value?.let { URLEncoder.encode(it, "UTF-8") } ?: "")
|
||||
|
||||
class ReCaptchaActivity : AppCompatActivity() {
|
||||
private val receiver: ResultReceiver?
|
||||
get() = intent?.getParcelableExtra("result") as ResultReceiver?
|
||||
private val params: String?
|
||||
get() = intent?.getStringExtra("params")
|
||||
private val webView: WebView?
|
||||
get() = findViewById(R.id.recaptcha_webview)
|
||||
private val loading: View?
|
||||
get() = findViewById(R.id.recaptcha_loading)
|
||||
private val density: Float
|
||||
get() = resources.displayMetrics.density
|
||||
private val widthPixels: Int
|
||||
get() = resources.displayMetrics.widthPixels
|
||||
private val heightPixels: Int
|
||||
get() {
|
||||
val base = resources.displayMetrics.heightPixels
|
||||
val statusBarHeightId = resources.getIdentifier("status_bar_height", "dimen", "android")
|
||||
val statusBarHeight = if (statusBarHeightId > 0) resources.getDimensionPixelSize(statusBarHeightId) else 0
|
||||
return base - statusBarHeight - (density * 20.0).toInt()
|
||||
}
|
||||
private var resultSent: Boolean = false
|
||||
|
||||
@SuppressLint("SetJavaScriptEnabled", "AddJavascriptInterface")
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
if (receiver == null || params == null) {
|
||||
finish()
|
||||
return
|
||||
}
|
||||
requestWindowFeature(Window.FEATURE_NO_TITLE)
|
||||
setContentView(R.layout.recaptcha_window)
|
||||
webView?.apply {
|
||||
webViewClient = object : WebViewClientCompat() {
|
||||
fun String.isRecaptchaUrl() = startsWith("https://www.gstatic.com/recaptcha/") || startsWith("https://www.google.com/recaptcha/") || startsWith("https://www.google.com/js/bg/")
|
||||
|
||||
override fun shouldInterceptRequest(view: WebView, url: String): WebResourceResponse? {
|
||||
if (url.isRecaptchaUrl()) {
|
||||
return null
|
||||
}
|
||||
return WebResourceResponse("text/plain", "UTF-8", ByteArrayInputStream(byteArrayOf()))
|
||||
}
|
||||
|
||||
override fun shouldOverrideUrlLoading(view: WebView, url: String): Boolean {
|
||||
if (url.startsWith("https://support.google.com/recaptcha")) {
|
||||
startActivity(Intent("android.intent.action.VIEW", Uri.parse(url)))
|
||||
finish()
|
||||
return true
|
||||
}
|
||||
return !url.isRecaptchaUrl()
|
||||
}
|
||||
}
|
||||
settings.apply {
|
||||
javaScriptEnabled = true
|
||||
useWideViewPort = true
|
||||
displayZoomControls = false
|
||||
setSupportZoom(false)
|
||||
cacheMode = WebSettings.LOAD_NO_CACHE
|
||||
ProfileManager.ensureInitialized(this@ReCaptchaActivity)
|
||||
userAgentString = Build.generateWebViewUserAgentString(userAgentString)
|
||||
}
|
||||
addJavascriptInterface(ReCaptchaEmbedder(this@ReCaptchaActivity), "RecaptchaEmbedder")
|
||||
}
|
||||
lifecycleScope.launchWhenResumed {
|
||||
open()
|
||||
}
|
||||
}
|
||||
|
||||
fun sendErrorResult(errorCode: Int, error: String) = sendResult(errorCode) { putString("error", error); putInt("errorCode", errorCode) }
|
||||
|
||||
fun sendResult(resultCode: Int, v: Bundle.() -> Unit) {
|
||||
receiver?.send(resultCode, Bundle().also(v))
|
||||
resultSent = true
|
||||
}
|
||||
|
||||
override fun finish() {
|
||||
lifecycleScope.launchWhenResumed {
|
||||
webView?.loadUrl("javascript: RecaptchaMFrame.shown(0, 0, false);")
|
||||
}
|
||||
if (!resultSent) {
|
||||
sendErrorResult(CANCELED, "Cancelled")
|
||||
}
|
||||
super.finish()
|
||||
}
|
||||
|
||||
fun setWebViewSize(width: Int, height: Int, visible: Boolean) {
|
||||
webView?.apply {
|
||||
layoutParams.width = min(widthPixels, (width * density).toInt())
|
||||
layoutParams.height = min(heightPixels, (height * density).toInt())
|
||||
requestLayout()
|
||||
loadUrl("javascript: RecaptchaMFrame.shown(${(layoutParams.width / density).toInt()}, ${(layoutParams.height / density).toInt()}, $visible);")
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun updateToken(flow: String, params: String) {
|
||||
val map = mapOf("contentBinding" to Base64.encodeToString(MessageDigest.getInstance("SHA-256").digest(params.toByteArray()), Base64.NO_WRAP))
|
||||
val dg = try {
|
||||
DroidGuardClient.getResults(this, flow, map).await()
|
||||
} catch (e: Exception) {
|
||||
Log.w(TAG, e)
|
||||
Base64.encodeToString("ERROR : IOException".toByteArray(), Base64.NO_WRAP + Base64.URL_SAFE + Base64.NO_PADDING)
|
||||
}
|
||||
if (SDK_INT >= 19) {
|
||||
webView?.evaluateJavascript("RecaptchaMFrame.token('${URLEncoder.encode(dg, "UTF-8")}', '$params');", null)
|
||||
} else {
|
||||
webView?.loadUrl("javascript: RecaptchaMFrame.token('${URLEncoder.encode(dg, "UTF-8")}', '$params');")
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun open() {
|
||||
val params = StringBuilder(params).appendUrlEncodedParam("mt", System.currentTimeMillis().toString()).toString()
|
||||
val map = mapOf("contentBinding" to Base64.encodeToString(MessageDigest.getInstance("SHA-256").digest(params.toByteArray()), Base64.NO_WRAP))
|
||||
val dg = try {
|
||||
DroidGuardClient.getResults(this, "recaptcha-android-frame", map).await()
|
||||
} catch (e: Exception) {
|
||||
Log.w(TAG, e)
|
||||
Base64.encodeToString("ERROR : IOException".toByteArray(), Base64.NO_WRAP + Base64.URL_SAFE + Base64.NO_PADDING)
|
||||
}
|
||||
webView?.postUrl(MFRAME_URL, "mav=1&dg=${URLEncoder.encode(dg, "UTF-8")}&mp=${URLEncoder.encode(params, "UTF-8")}".toByteArray())
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val MFRAME_URL = "https://www.google.com/recaptcha/api2/mframe"
|
||||
|
||||
class ReCaptchaEmbedder(val activity: ReCaptchaActivity) {
|
||||
@JavascriptInterface
|
||||
fun challengeReady() {
|
||||
Log.d(TAG, "challengeReady()")
|
||||
activity.runOnUiThread { activity.webView?.loadUrl("javascript: RecaptchaMFrame.show(${min(activity.widthPixels / activity.density, 400f)}, ${min(activity.heightPixels / activity.density, 400f)});") }
|
||||
}
|
||||
|
||||
@JavascriptInterface
|
||||
fun getClientAPIVersion() = 1
|
||||
|
||||
@JavascriptInterface
|
||||
fun onChallengeExpired() {
|
||||
Log.d(TAG, "onChallengeExpired()")
|
||||
}
|
||||
|
||||
@JavascriptInterface
|
||||
fun onError(errorCode: Int, finish: Boolean) {
|
||||
Log.d(TAG, "onError($errorCode, $finish)")
|
||||
when (errorCode) {
|
||||
1 -> activity.sendErrorResult(ERROR, "Invalid Input Argument")
|
||||
2 -> activity.sendErrorResult(TIMEOUT, "Session Timeout")
|
||||
7 -> activity.sendErrorResult(RECAPTCHA_INVALID_SITEKEY, "Invalid Site Key")
|
||||
8 -> activity.sendErrorResult(RECAPTCHA_INVALID_KEYTYPE, "Invalid Type of Site Key")
|
||||
9 -> activity.sendErrorResult(RECAPTCHA_INVALID_PACKAGE_NAME, "Invalid Package Name for App")
|
||||
else -> activity.sendErrorResult(ERROR, "error")
|
||||
}
|
||||
if (finish) activity.finish()
|
||||
}
|
||||
|
||||
@JavascriptInterface
|
||||
fun onResize(width: Int, height: Int) {
|
||||
Log.d(TAG, "onResize($width, $height)")
|
||||
if (activity.webView?.visibility == View.VISIBLE) {
|
||||
activity.runOnUiThread { activity.setWebViewSize(width, height, true) }
|
||||
} else {
|
||||
activity.runOnUiThread { activity.webView?.loadUrl("javascript: RecaptchaMFrame.shown($width, $height, true);") }
|
||||
}
|
||||
}
|
||||
|
||||
@JavascriptInterface
|
||||
fun onShow(visible: Boolean, width: Int, height: Int) {
|
||||
Log.d(TAG, "onShow($visible, $width, $height)")
|
||||
if (width <= 0 && height <= 0) {
|
||||
activity.runOnUiThread { activity.webView?.loadUrl("javascript: RecaptchaMFrame.shown($width, $height, $visible);") }
|
||||
} else {
|
||||
activity.runOnUiThread {
|
||||
activity.setWebViewSize(width, height, visible)
|
||||
activity.loading?.visibility = if (visible) View.GONE else View.VISIBLE
|
||||
activity.webView?.visibility = if (visible) View.VISIBLE else View.GONE
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@JavascriptInterface
|
||||
fun requestToken(s: String, b: Boolean) {
|
||||
Log.d(TAG, "requestToken($s, $b)")
|
||||
activity.runOnUiThread {
|
||||
val cert = activity.webView?.certificate?.let { Base64.encodeToString(SslCertificate.saveState(it).getByteArray("x509-certificate"), Base64.URL_SAFE + Base64.NO_PADDING + Base64.NO_WRAP) }
|
||||
?: ""
|
||||
val params = StringBuilder(activity.params).appendUrlEncodedParam("c", s).appendUrlEncodedParam("sc", cert).appendUrlEncodedParam("mt", System.currentTimeMillis().toString()).toString()
|
||||
val flow = "recaptcha-android-${if (b) "verify" else "reload"}"
|
||||
activity.lifecycleScope.launchWhenResumed {
|
||||
activity.updateToken(flow, params)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@JavascriptInterface
|
||||
fun verifyCallback(token: String) {
|
||||
Log.d(TAG, "verifyCallback($token)")
|
||||
activity.sendResult(0) { putString("token", token) }
|
||||
activity.resultSent = true
|
||||
activity.finish()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,277 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2021 microG Project Team
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
package org.microg.gms.safetynet
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.database.Cursor
|
||||
import android.os.Build.VERSION.SDK_INT
|
||||
import android.os.Bundle
|
||||
import android.os.Parcel
|
||||
import android.os.ResultReceiver
|
||||
import android.util.Base64
|
||||
import android.util.Log
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.LifecycleOwner
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import com.google.android.gms.common.api.Status
|
||||
import com.google.android.gms.common.internal.GetServiceRequest
|
||||
import com.google.android.gms.common.internal.IGmsCallbacks
|
||||
import com.google.android.gms.droidguard.DroidGuardClient
|
||||
import com.google.android.gms.safetynet.AttestationData
|
||||
import com.google.android.gms.safetynet.HarmfulAppsInfo
|
||||
import com.google.android.gms.safetynet.RecaptchaResultData
|
||||
import com.google.android.gms.safetynet.SafeBrowsingData
|
||||
import com.google.android.gms.safetynet.SafetyNetStatusCodes
|
||||
import com.google.android.gms.safetynet.internal.ISafetyNetCallbacks
|
||||
import com.google.android.gms.safetynet.internal.ISafetyNetService
|
||||
import com.google.android.gms.tasks.await
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.microg.gms.BaseService
|
||||
import org.microg.gms.common.GmsService
|
||||
import org.microg.gms.common.GooglePackagePermission
|
||||
import org.microg.gms.common.PackageUtils
|
||||
import org.microg.gms.droidguard.core.DroidGuardPreferences
|
||||
import org.microg.gms.settings.SettingsContract
|
||||
import org.microg.gms.settings.SettingsContract.CheckIn.getContentUri
|
||||
import org.microg.gms.settings.SettingsContract.getSettings
|
||||
import org.microg.gms.utils.warnOnTransactionIssues
|
||||
import java.io.IOException
|
||||
import java.net.URLEncoder
|
||||
|
||||
private const val TAG = "GmsSafetyNet"
|
||||
private const val DEFAULT_API_KEY = "AIzaSyDqVnJBjE5ymo--oBJt3On7HQx9xNm1RHA"
|
||||
|
||||
class SafetyNetClientService : BaseService(TAG, GmsService.SAFETY_NET_CLIENT) {
|
||||
override fun handleServiceRequest(callback: IGmsCallbacks, request: GetServiceRequest, service: GmsService) {
|
||||
callback.onPostInitComplete(0, SafetyNetClientServiceImpl(this, request.packageName, lifecycle), null)
|
||||
}
|
||||
}
|
||||
|
||||
private fun StringBuilder.appendUrlEncodedParam(key: String, value: String?) = append("&")
|
||||
.append(URLEncoder.encode(key, "UTF-8"))
|
||||
.append("=")
|
||||
.append(value?.let { URLEncoder.encode(it, "UTF-8") } ?: "")
|
||||
|
||||
class SafetyNetClientServiceImpl(
|
||||
private val context: Context,
|
||||
private val packageName: String,
|
||||
override val lifecycle: Lifecycle
|
||||
) : ISafetyNetService.Stub(), LifecycleOwner {
|
||||
|
||||
override fun attest(callbacks: ISafetyNetCallbacks, nonce: ByteArray) {
|
||||
attestWithApiKey(callbacks, nonce, DEFAULT_API_KEY)
|
||||
}
|
||||
|
||||
override fun attestWithApiKey(callbacks: ISafetyNetCallbacks, nonce: ByteArray?, apiKey: String) {
|
||||
if (nonce == null) {
|
||||
callbacks.onAttestationResult(Status(SafetyNetStatusCodes.DEVELOPER_ERROR, "Nonce missing"), null)
|
||||
return
|
||||
}
|
||||
|
||||
if (!SafetyNetPreferences.isEnabled(context)) {
|
||||
Log.d(TAG, "ignoring SafetyNet request, SafetyNet is disabled")
|
||||
callbacks.onAttestationResult(Status(SafetyNetStatusCodes.ERROR, "Disabled"), null)
|
||||
return
|
||||
}
|
||||
|
||||
if (!DroidGuardPreferences.isAvailable(context)) {
|
||||
Log.d(TAG, "ignoring SafetyNet request, DroidGuard is disabled")
|
||||
callbacks.onAttestationResult(Status(SafetyNetStatusCodes.ERROR, "Unsupported"), null)
|
||||
return
|
||||
}
|
||||
|
||||
lifecycleScope.launchWhenStarted {
|
||||
val db = SafetyNetDatabase(context)
|
||||
var requestID: Long = -1
|
||||
try {
|
||||
val attestation = Attestation(context, packageName)
|
||||
val safetyNetData = attestation.buildPayload(nonce)
|
||||
|
||||
requestID = db.insertRecentRequestStart(
|
||||
SafetyNetRequestType.ATTESTATION,
|
||||
safetyNetData.packageName,
|
||||
safetyNetData.nonce?.toByteArray(),
|
||||
safetyNetData.currentTimeMs ?: 0
|
||||
)
|
||||
|
||||
val data = mapOf("contentBinding" to attestation.payloadHashBase64)
|
||||
val dg = withContext(Dispatchers.IO) { DroidGuardClient.getResults(context, "attest", data).await() }
|
||||
attestation.setDroidGuardResult(dg)
|
||||
val jwsResult = withContext(Dispatchers.IO) { attestation.attest(apiKey) }
|
||||
|
||||
|
||||
val jsonData = try {
|
||||
requireNotNull(jwsResult)
|
||||
jwsResult.split(".").let {
|
||||
assert(it.size == 3)
|
||||
return@let Base64.decode(it[1], Base64.URL_SAFE).decodeToString()
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
Log.w(TAG, "An exception occurred when parsing the JWS token.")
|
||||
null
|
||||
}
|
||||
|
||||
db.insertRecentRequestEnd(requestID, Status.SUCCESS, jsonData)
|
||||
callbacks.onAttestationResult(Status.SUCCESS, AttestationData(jwsResult))
|
||||
} catch (e: Exception) {
|
||||
Log.w(TAG, "Exception during attest: ${e.javaClass.name}", e)
|
||||
val code = when (e) {
|
||||
is IOException -> SafetyNetStatusCodes.NETWORK_ERROR
|
||||
else -> SafetyNetStatusCodes.ERROR
|
||||
}
|
||||
val status = Status(code, e.localizedMessage)
|
||||
|
||||
// This shouldn't happen, but do not update the database if it didn't insert the start of the request
|
||||
if (requestID != -1L) db.insertRecentRequestEnd(requestID, status, null)
|
||||
try {
|
||||
callbacks.onAttestationResult(Status(code, e.localizedMessage), null)
|
||||
} catch (e: Exception) {
|
||||
Log.w(TAG, "Exception while sending error", e)
|
||||
}
|
||||
}
|
||||
db.close()
|
||||
}
|
||||
}
|
||||
|
||||
override fun getSharedUuid(callbacks: ISafetyNetCallbacks) {
|
||||
PackageUtils.checkPackageUid(context, packageName, getCallingUid())
|
||||
PackageUtils.assertGooglePackagePermission(context, GooglePackagePermission.SAFETYNET)
|
||||
|
||||
// TODO
|
||||
Log.d(TAG, "dummy Method: getSharedUuid")
|
||||
callbacks.onSharedUuid("aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa")
|
||||
}
|
||||
|
||||
override fun lookupUri(callbacks: ISafetyNetCallbacks, apiKey: String, threatTypes: IntArray, i: Int, s2: String) {
|
||||
Log.d(TAG, "unimplemented Method: lookupUri")
|
||||
callbacks.onSafeBrowsingData(Status.SUCCESS, SafeBrowsingData())
|
||||
}
|
||||
|
||||
override fun enableVerifyApps(callbacks: ISafetyNetCallbacks) {
|
||||
Log.d(TAG, "dummy Method: enableVerifyApps")
|
||||
callbacks.onVerifyAppsUserResult(Status.SUCCESS, true)
|
||||
}
|
||||
|
||||
override fun listHarmfulApps(callbacks: ISafetyNetCallbacks) {
|
||||
Log.d(TAG, "dummy Method: listHarmfulApps")
|
||||
callbacks.onHarmfulAppsInfo(Status.SUCCESS, HarmfulAppsInfo().apply {
|
||||
lastScanTime = ((System.currentTimeMillis() - VERIFY_APPS_LAST_SCAN_DELAY) / VERIFY_APPS_LAST_SCAN_TIME_ROUNDING) * VERIFY_APPS_LAST_SCAN_TIME_ROUNDING + VERIFY_APPS_LAST_SCAN_OFFSET
|
||||
})
|
||||
}
|
||||
|
||||
override fun verifyWithRecaptcha(callbacks: ISafetyNetCallbacks, siteKey: String?) {
|
||||
if (siteKey == null) {
|
||||
callbacks.onRecaptchaResult(Status(SafetyNetStatusCodes.RECAPTCHA_INVALID_SITEKEY, "SiteKey missing"), null)
|
||||
return
|
||||
}
|
||||
|
||||
if (!SafetyNetPreferences.isEnabled(context)) {
|
||||
Log.d(TAG, "ignoring SafetyNet request, SafetyNet is disabled")
|
||||
callbacks.onRecaptchaResult(Status(SafetyNetStatusCodes.ERROR, "Disabled"), null)
|
||||
return
|
||||
}
|
||||
|
||||
val db = SafetyNetDatabase(context)
|
||||
val requestID = db.insertRecentRequestStart(
|
||||
SafetyNetRequestType.RECAPTCHA,
|
||||
context.packageName,
|
||||
null,
|
||||
System.currentTimeMillis()
|
||||
)
|
||||
|
||||
val intent = Intent("org.microg.gms.safetynet.RECAPTCHA_ACTIVITY")
|
||||
intent.`package` = context.packageName
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY)
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS)
|
||||
val androidId = getSettings(
|
||||
context,
|
||||
getContentUri(context),
|
||||
arrayOf(SettingsContract.CheckIn.ANDROID_ID)
|
||||
) { cursor: Cursor -> cursor.getLong(0) }
|
||||
val params = StringBuilder()
|
||||
|
||||
val (packageFileDigest, packageSignatures) = try {
|
||||
Pair(
|
||||
Base64.encodeToString(
|
||||
Attestation.getPackageFileDigest(context, packageName),
|
||||
Base64.URL_SAFE or Base64.NO_WRAP or Base64.NO_PADDING
|
||||
),
|
||||
Attestation.getPackageSignatures(context, packageName)
|
||||
.map { Base64.encodeToString(it, Base64.URL_SAFE or Base64.NO_WRAP or Base64.NO_PADDING) }
|
||||
)
|
||||
} catch (e: Exception) {
|
||||
db.insertRecentRequestEnd(requestID, Status(SafetyNetStatusCodes.ERROR, e.localizedMessage), null)
|
||||
db.close()
|
||||
callbacks.onRecaptchaResult(Status(SafetyNetStatusCodes.ERROR, e.localizedMessage), null)
|
||||
return
|
||||
}
|
||||
|
||||
params.appendUrlEncodedParam("k", siteKey)
|
||||
.appendUrlEncodedParam("di", androidId.toString())
|
||||
.appendUrlEncodedParam("pk", packageName)
|
||||
.appendUrlEncodedParam("sv", SDK_INT.toString())
|
||||
.appendUrlEncodedParam("gv", "20.47.14 (040306-{{cl}})")
|
||||
.appendUrlEncodedParam("gm", "260")
|
||||
.appendUrlEncodedParam("as", packageFileDigest)
|
||||
for (signature in packageSignatures) {
|
||||
Log.d(TAG, "Sig: $signature")
|
||||
params.appendUrlEncodedParam("ac", signature)
|
||||
}
|
||||
params.appendUrlEncodedParam("ip", "com.android.vending")
|
||||
.appendUrlEncodedParam("av", false.toString())
|
||||
.appendUrlEncodedParam("si", null)
|
||||
intent.putExtra("params", params.toString())
|
||||
intent.putExtra("result", object : ResultReceiver(null) {
|
||||
override fun onReceiveResult(resultCode: Int, resultData: Bundle) {
|
||||
if (resultCode != 0) {
|
||||
db.insertRecentRequestEnd(
|
||||
requestID,
|
||||
Status(resultData.getInt("errorCode"), resultData.getString("error")),
|
||||
null
|
||||
)
|
||||
db.close()
|
||||
callbacks.onRecaptchaResult(
|
||||
Status(resultData.getInt("errorCode"), resultData.getString("error")),
|
||||
null
|
||||
)
|
||||
} else {
|
||||
db.insertRecentRequestEnd(requestID, Status.SUCCESS, resultData.getString("token"))
|
||||
db.close()
|
||||
callbacks.onRecaptchaResult(
|
||||
Status.SUCCESS,
|
||||
RecaptchaResultData().apply { token = resultData.getString("token") })
|
||||
}
|
||||
}
|
||||
})
|
||||
context.startActivity(intent)
|
||||
}
|
||||
|
||||
override fun initSafeBrowsing(callbacks: ISafetyNetCallbacks) {
|
||||
Log.d(TAG, "dummy: initSafeBrowsing")
|
||||
callbacks.onInitSafeBrowsingResult(Status.SUCCESS)
|
||||
}
|
||||
|
||||
override fun shutdownSafeBrowsing() {
|
||||
Log.d(TAG, "dummy: shutdownSafeBrowsing")
|
||||
}
|
||||
|
||||
override fun isVerifyAppsEnabled(callbacks: ISafetyNetCallbacks) {
|
||||
Log.d(TAG, "dummy: isVerifyAppsEnabled")
|
||||
callbacks.onVerifyAppsUserResult(Status.SUCCESS, true)
|
||||
}
|
||||
|
||||
override fun onTransact(code: Int, data: Parcel, reply: Parcel?, flags: Int): Boolean = warnOnTransactionIssues(code, reply, flags, TAG) { super.onTransact(code, data, reply, flags) }
|
||||
|
||||
companion object {
|
||||
// We simulate one scan every day, which will happen at 03:12:02.121 and will be available 32 seconds later
|
||||
const val VERIFY_APPS_LAST_SCAN_DELAY = 32 * 1000L
|
||||
const val VERIFY_APPS_LAST_SCAN_OFFSET = ((3 * 60 + 12) * 60 + 2) * 1000L + 121
|
||||
const val VERIFY_APPS_LAST_SCAN_TIME_ROUNDING = 24 * 60 * 60 * 1000L
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,154 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2016, microG Project Team
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
package org.microg.gms.safetynet
|
||||
|
||||
import android.content.ContentValues
|
||||
import android.content.Context
|
||||
import android.database.Cursor
|
||||
import android.database.sqlite.SQLiteDatabase
|
||||
import android.database.sqlite.SQLiteOpenHelper
|
||||
import android.os.Build.VERSION.SDK_INT
|
||||
import android.util.Log
|
||||
import com.google.android.gms.common.api.Status
|
||||
|
||||
class SafetyNetDatabase(context: Context) : SQLiteOpenHelper(context, DB_NAME, null, DB_VERSION) {
|
||||
|
||||
init {
|
||||
if (SDK_INT >= 16) {
|
||||
setWriteAheadLoggingEnabled(true)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onOpen(db: SQLiteDatabase) {
|
||||
if (!db.isReadOnly) clearOldRequests(db)
|
||||
}
|
||||
|
||||
private fun createSafetyNetSummary(cursor: Cursor): SafetyNetSummary {
|
||||
val summary = SafetyNetSummary(
|
||||
SafetyNetRequestType.valueOf(
|
||||
cursor.getString(cursor.getColumnIndexOrThrow(FIELD_REQUEST_TYPE))
|
||||
),
|
||||
cursor.getString(cursor.getColumnIndexOrThrow(FIELD_PACKAGE_NAME)),
|
||||
cursor.getBlob(cursor.getColumnIndexOrThrow(FIELD_NONCE)),
|
||||
cursor.getLong(cursor.getColumnIndexOrThrow(FIELD_TIMESTAMP))
|
||||
)
|
||||
summary.id = cursor.getInt(cursor.getColumnIndexOrThrow(FIELD_ID))
|
||||
if (cursor.isNull(cursor.getColumnIndexOrThrow(FIELD_RESULT_STATUS_CODE))) return summary
|
||||
summary.responseStatus = Status(
|
||||
cursor.getInt(cursor.getColumnIndexOrThrow(FIELD_RESULT_STATUS_CODE)),
|
||||
cursor.getString(cursor.getColumnIndexOrThrow(FIELD_RESULT_STATUS_MSG))
|
||||
)
|
||||
summary.responseData = cursor.getString(cursor.getColumnIndexOrThrow(FIELD_RESULT_DATA))
|
||||
return summary
|
||||
}
|
||||
|
||||
val recentApps: List<Pair<String, Long>>
|
||||
get() {
|
||||
val db = readableDatabase
|
||||
val cursor = db.query(TABLE_RECENTS, arrayOf(FIELD_PACKAGE_NAME, "MAX($FIELD_TIMESTAMP)"), null, null, FIELD_PACKAGE_NAME, null, "MAX($FIELD_TIMESTAMP) DESC")
|
||||
if (cursor != null) {
|
||||
val result = ArrayList<Pair<String, Long>>()
|
||||
while (cursor.moveToNext()) {
|
||||
result.add(cursor.getString(0) to cursor.getLong(1))
|
||||
}
|
||||
cursor.close()
|
||||
return result
|
||||
}
|
||||
return emptyList()
|
||||
}
|
||||
|
||||
fun getRecentRequests(packageName: String): List<SafetyNetSummary> {
|
||||
val db = readableDatabase
|
||||
val cursor = db.query(TABLE_RECENTS, null, "$FIELD_PACKAGE_NAME = ?", arrayOf(packageName), null, null, "$FIELD_TIMESTAMP DESC")
|
||||
if (cursor != null) {
|
||||
val result: MutableList<SafetyNetSummary> = ArrayList()
|
||||
while (cursor.moveToNext()) {
|
||||
result.add(createSafetyNetSummary(cursor))
|
||||
}
|
||||
cursor.close()
|
||||
return result
|
||||
}
|
||||
return emptyList()
|
||||
}
|
||||
|
||||
fun insertRecentRequestStart(
|
||||
requestType: SafetyNetRequestType,
|
||||
packageName: String?,
|
||||
nonce: ByteArray?,
|
||||
timestamp: Long
|
||||
): Long {
|
||||
val db = writableDatabase
|
||||
val cv = ContentValues()
|
||||
cv.put(FIELD_REQUEST_TYPE, requestType.name)
|
||||
cv.put(FIELD_PACKAGE_NAME, packageName)
|
||||
cv.put(FIELD_NONCE, nonce)
|
||||
cv.put(FIELD_TIMESTAMP, timestamp)
|
||||
return db.insert(TABLE_RECENTS, null, cv)
|
||||
}
|
||||
|
||||
fun insertRecentRequestEnd(id: Long, status: Status, resultData: String?) {
|
||||
val db = writableDatabase
|
||||
val cv = ContentValues()
|
||||
cv.put(FIELD_RESULT_STATUS_CODE, status.statusCode)
|
||||
cv.put(FIELD_RESULT_STATUS_MSG, status.statusMessage)
|
||||
cv.put(FIELD_RESULT_DATA, resultData)
|
||||
db.update(TABLE_RECENTS, cv, "$FIELD_ID = ?", arrayOf(id.toString()))
|
||||
}
|
||||
|
||||
fun clearOldRequests(db: SQLiteDatabase) {
|
||||
val timeout = 1000 * 60 * 60 * 24 * 14 // 14 days
|
||||
val maxRequests = 150
|
||||
var rows = 0
|
||||
|
||||
rows += db.compileStatement(
|
||||
"DELETE FROM $TABLE_RECENTS WHERE $FIELD_ID NOT IN " +
|
||||
"(SELECT $FIELD_ID FROM $TABLE_RECENTS ORDER BY $FIELD_TIMESTAMP LIMIT $maxRequests)"
|
||||
).executeUpdateDelete()
|
||||
|
||||
val sqLiteStatement = db.compileStatement("DELETE FROM $TABLE_RECENTS WHERE $FIELD_TIMESTAMP + ? < ?")
|
||||
sqLiteStatement.bindLong(1, timeout.toLong())
|
||||
sqLiteStatement.bindLong(2, System.currentTimeMillis())
|
||||
rows += sqLiteStatement.executeUpdateDelete()
|
||||
|
||||
if (rows != 0) Log.d(TAG, "Cleared $rows old request(s)")
|
||||
}
|
||||
|
||||
fun clearAllRequests() {
|
||||
val db = writableDatabase
|
||||
db.execSQL("DELETE FROM $TABLE_RECENTS")
|
||||
}
|
||||
|
||||
override fun onCreate(db: SQLiteDatabase) {
|
||||
db.execSQL(CREATE_TABLE_RECENTS)
|
||||
}
|
||||
|
||||
override fun onUpgrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) {
|
||||
throw IllegalStateException("Upgrades not supported")
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val TAG = SafetyNetDatabase::class.java.simpleName
|
||||
private const val DB_NAME = "snet.db"
|
||||
private const val DB_VERSION = 1
|
||||
private const val CREATE_TABLE_RECENTS = "CREATE TABLE recents (" +
|
||||
"id INTEGER PRIMARY KEY AUTOINCREMENT ," +
|
||||
"request_type TEXT," +
|
||||
"package_name TEXT," +
|
||||
"nonce TEXT," +
|
||||
"timestamp INTEGER," +
|
||||
"result_status_code INTEGER DEFAULT NULL," +
|
||||
"result_status_msg TEXT DEFAULT NULL," +
|
||||
"result_data TEXT DEFAULT NULL)"
|
||||
private const val TABLE_RECENTS = "recents"
|
||||
private const val FIELD_ID = "id"
|
||||
private const val FIELD_REQUEST_TYPE = "request_type"
|
||||
private const val FIELD_PACKAGE_NAME = "package_name"
|
||||
private const val FIELD_NONCE = "nonce"
|
||||
private const val FIELD_TIMESTAMP = "timestamp"
|
||||
private const val FIELD_RESULT_STATUS_CODE = "result_status_code"
|
||||
private const val FIELD_RESULT_STATUS_MSG = "result_status_msg"
|
||||
private const val FIELD_RESULT_DATA = "result_data"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2021 microG Project Team
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package org.microg.gms.safetynet
|
||||
|
||||
import android.content.ContentValues
|
||||
import android.content.Context
|
||||
import android.database.Cursor
|
||||
import org.microg.gms.settings.SettingsContract
|
||||
import org.microg.gms.settings.SettingsContract.SafetyNet.ENABLED
|
||||
|
||||
object SafetyNetPreferences {
|
||||
private fun <T> getSettings(context: Context, projection: String, def: T, f: (Cursor) -> T): T {
|
||||
return try {
|
||||
SettingsContract.getSettings(context, SettingsContract.SafetyNet.getContentUri(context), arrayOf(projection), f)
|
||||
} catch (e: Exception) {
|
||||
def
|
||||
}
|
||||
}
|
||||
|
||||
private fun setSettings(context: Context, f: ContentValues.() -> Unit) =
|
||||
SettingsContract.setSettings(context, SettingsContract.SafetyNet.getContentUri(context), f)
|
||||
|
||||
@JvmStatic
|
||||
fun isEnabled(context: Context): Boolean = getSettings(context, ENABLED, false) { it.getInt(0) != 0 }
|
||||
|
||||
@JvmStatic
|
||||
fun setEnabled(context: Context, enabled: Boolean) = setSettings(context) { put(ENABLED, enabled) }
|
||||
}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
package org.microg.gms.safetynet
|
||||
|
||||
enum class SafetyNetRequestType {
|
||||
ATTESTATION,
|
||||
RECAPTCHA,
|
||||
RECAPTCHA_ENTERPRISE,
|
||||
;
|
||||
}
|
||||
|
|
@ -0,0 +1,87 @@
|
|||
package org.microg.gms.safetynet
|
||||
|
||||
import android.os.Parcel
|
||||
import android.os.Parcelable
|
||||
import com.google.android.gms.common.api.Status
|
||||
import kotlin.properties.Delegates
|
||||
|
||||
data class SafetyNetSummary(
|
||||
val requestType: SafetyNetRequestType,
|
||||
|
||||
|
||||
// request data
|
||||
val packageName: String,
|
||||
val nonce: ByteArray?, // null with SafetyNetRequestType::RECAPTCHA
|
||||
val timestamp: Long,
|
||||
|
||||
) : Parcelable {
|
||||
|
||||
var id by Delegates.notNull<Int>()
|
||||
|
||||
// response data
|
||||
// note : responseStatus do not represent the actual status in case of an attestation, it will be in resultData
|
||||
var responseStatus: Status? = null
|
||||
var responseData: String? = null
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (javaClass != other?.javaClass) return false
|
||||
|
||||
other as SafetyNetSummary
|
||||
|
||||
if (requestType != other.requestType) return false
|
||||
if (packageName != other.packageName) return false
|
||||
if (!nonce.contentEquals(other.nonce)) return false
|
||||
if (responseStatus != other.responseStatus) return false
|
||||
if (responseData != other.responseData) return false
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
var result = requestType.hashCode()
|
||||
result = 31 * result + packageName.hashCode()
|
||||
result = 31 * result + nonce.hashCode()
|
||||
result = 31 * result + (responseStatus?.hashCode() ?: 0)
|
||||
result = 31 * result + (responseData?.hashCode() ?: 0)
|
||||
return result
|
||||
}
|
||||
|
||||
|
||||
// Parcelable implementation
|
||||
|
||||
constructor(parcel: Parcel) : this(
|
||||
SafetyNetRequestType.valueOf(parcel.readString()!!),
|
||||
parcel.readString()!!,
|
||||
parcel.createByteArray(),
|
||||
parcel.readLong()
|
||||
) {
|
||||
responseStatus = parcel.readParcelable(Status::class.java.classLoader)
|
||||
responseData = parcel.readString()
|
||||
}
|
||||
|
||||
override fun writeToParcel(parcel: Parcel, flags: Int) {
|
||||
parcel.writeString(requestType.name)
|
||||
parcel.writeString(packageName)
|
||||
parcel.writeByteArray(nonce)
|
||||
parcel.writeLong(timestamp)
|
||||
parcel.writeParcelable(responseStatus, flags)
|
||||
parcel.writeString(responseData)
|
||||
}
|
||||
|
||||
override fun describeContents(): Int {
|
||||
return 0
|
||||
}
|
||||
|
||||
companion object CREATOR : Parcelable.Creator<SafetyNetSummary> {
|
||||
override fun createFromParcel(parcel: Parcel): SafetyNetSummary {
|
||||
return SafetyNetSummary(parcel)
|
||||
}
|
||||
|
||||
override fun newArray(size: Int): Array<SafetyNetSummary?> {
|
||||
return arrayOfNulls(size)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
34
play-services-safetynet/core/src/main/proto/safetynet.proto
Normal file
34
play-services-safetynet/core/src/main/proto/safetynet.proto
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
option java_package = "org.microg.gms.safetynet";
|
||||
|
||||
option java_outer_classname = "SafetyNetProto";
|
||||
|
||||
message SELinuxState {
|
||||
optional bool supported = 1;
|
||||
optional bool enabled = 2;
|
||||
}
|
||||
|
||||
message FileState {
|
||||
optional string fileName = 1;
|
||||
optional bytes digest = 2;
|
||||
}
|
||||
|
||||
message SafetyNetData {
|
||||
optional bytes nonce = 1;
|
||||
optional string packageName = 2;
|
||||
repeated bytes signatureDigest = 3;
|
||||
optional bytes fileDigest = 4;
|
||||
optional int32 gmsVersionCode = 5;
|
||||
repeated FileState suCandidates = 6;
|
||||
optional SELinuxState seLinuxState = 7;
|
||||
optional int64 currentTimeMs = 8;
|
||||
optional bool googleCn = 9;
|
||||
}
|
||||
|
||||
message AttestRequest {
|
||||
optional bytes safetyNetData = 1;
|
||||
optional string droidGuardResult = 2;
|
||||
}
|
||||
|
||||
message AttestResponse {
|
||||
optional string result = 2;
|
||||
}
|
||||
|
|
@ -0,0 +1,64 @@
|
|||
<!--
|
||||
~ SPDX-FileCopyrightText: 2021, microG Project Team
|
||||
~ SPDX-License-Identifier: Apache-2.0
|
||||
-->
|
||||
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:aapt="http://schemas.android.com/aapt"
|
||||
android:width="48dp"
|
||||
android:height="48dp"
|
||||
android:viewportWidth="48"
|
||||
android:viewportHeight="48">
|
||||
<path
|
||||
android:pathData="M2,24L2,40.5L5.9387,36.5616a22,22 45,0 0,9.6423 7.7639,22 22,0 0,0 23.9753,-4.7692L31.071,31.071A10,10 0,0 1,24 34,10 10,135 0,1 14.7361,27.7642L18.5,24L14,24Z"
|
||||
android:strokeWidth="0"
|
||||
android:fillColor="#bdbdbd"
|
||||
android:strokeColor="#000000"/>
|
||||
<path
|
||||
android:pathData="M7.5,2 L11.4386,5.9387A22,22 0,0 0,2 24L14,24A10,10 135,0 1,20.236 14.7361L24,18.5L24,14 24,2l-0.0682,0z"
|
||||
android:strokeWidth="0"
|
||||
android:strokeColor="#000000">
|
||||
<aapt:attr name="android:fillColor">
|
||||
<gradient
|
||||
android:startY="24"
|
||||
android:startX="14"
|
||||
android:endY="20.75"
|
||||
android:endX="14.000"
|
||||
android:type="linear">
|
||||
<item android:offset="0" android:color="#FF1E88E5"/>
|
||||
<item android:offset="1" android:color="#FF2196F3"/>
|
||||
</gradient>
|
||||
</aapt:attr>
|
||||
</path>
|
||||
<path
|
||||
android:pathData="M46,7.5 L42.0615,11.4386A22,22 0,0 0,24 2V14a10,10 0,0 1,9.264 6.2358l-3.7641,3.7641h4.5,12v-0.0682z"
|
||||
android:strokeWidth="0"
|
||||
android:strokeColor="#000000">
|
||||
<aapt:attr name="android:fillColor">
|
||||
<gradient
|
||||
android:startY="14"
|
||||
android:startX="24"
|
||||
android:endY="14"
|
||||
android:endX="27.25"
|
||||
android:type="linear">
|
||||
<item android:offset="0" android:color="#FF3949AB"/>
|
||||
<item android:offset="1" android:color="#FF3F51B5"/>
|
||||
</gradient>
|
||||
</aapt:attr>
|
||||
</path>
|
||||
<path
|
||||
android:pathData="M46,7.5 L42.0615,11.4386C37.9491,5.5255 31.2026,2 24,2L46,24v-0.0682z"
|
||||
android:strokeWidth="0">
|
||||
<aapt:attr name="android:fillColor">
|
||||
<gradient
|
||||
android:startY="2"
|
||||
android:startX="24"
|
||||
android:endY="24"
|
||||
android:endX="46"
|
||||
android:type="linear">
|
||||
<item android:offset="0" android:color="#18FFFFFF"/>
|
||||
<item android:offset="1" android:color="#00FFFFFF"/>
|
||||
</gradient>
|
||||
</aapt:attr>
|
||||
</path>
|
||||
</vector>
|
||||
|
|
@ -0,0 +1,68 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
~ SPDX-FileCopyrightText: 2021, microG Project Team
|
||||
~ SPDX-License-Identifier: Apache-2.0
|
||||
-->
|
||||
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical">
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/recaptcha_loading"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@android:color/white"
|
||||
android:layout_gravity="center"
|
||||
android:gravity="center"
|
||||
android:orientation="vertical">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:layout_marginLeft="20dip"
|
||||
android:layout_marginTop="20dip"
|
||||
android:layout_marginRight="20dip"
|
||||
android:layout_marginBottom="10sp"
|
||||
android:gravity="center"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<ImageView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:src="@drawable/ic_recaptcha" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:layout_marginStart="10sp"
|
||||
android:layout_marginLeft="10sp"
|
||||
android:text="reCAPTCHA"
|
||||
android:textColor="#FF3949AB"
|
||||
android:textStyle="bold" />
|
||||
</LinearLayout>
|
||||
|
||||
<ProgressBar
|
||||
style="?android:progressBarStyleHorizontal"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:layout_marginLeft="20dip"
|
||||
android:layout_marginTop="10sp"
|
||||
android:layout_marginRight="20dip"
|
||||
android:layout_marginBottom="20dip"
|
||||
android:indeterminate="true"
|
||||
android:indeterminateTint="#FF3949AB" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<WebView
|
||||
android:id="@+id/recaptcha_webview"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:visibility="gone" />
|
||||
</FrameLayout>
|
||||
6
play-services-safetynet/src/main/AndroidManifest.xml
Normal file
6
play-services-safetynet/src/main/AndroidManifest.xml
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
~ SPDX-FileCopyrightText: 2022, microG Project Team
|
||||
~ SPDX-License-Identifier: Apache-2.0
|
||||
-->
|
||||
<manifest />
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
package com.google.android.gms.safetynet;
|
||||
|
||||
parcelable AttestationData;
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
package com.google.android.gms.safetynet;
|
||||
|
||||
parcelable HarmfulAppsData;
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
package com.google.android.gms.safetynet;
|
||||
|
||||
parcelable HarmfulAppsInfo;
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
package com.google.android.gms.safetynet;
|
||||
|
||||
parcelable RecaptchaResultData;
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
package com.google.android.gms.safetynet;
|
||||
|
||||
parcelable RemoveHarmfulAppData;
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
package com.google.android.gms.safetynet;
|
||||
|
||||
parcelable SafeBrowsingData;
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
package com.google.android.gms.safetynet.internal;
|
||||
|
||||
import com.google.android.gms.common.api.Status;
|
||||
import com.google.android.gms.safetynet.AttestationData;
|
||||
import com.google.android.gms.safetynet.HarmfulAppsData;
|
||||
import com.google.android.gms.safetynet.HarmfulAppsInfo;
|
||||
import com.google.android.gms.safetynet.RecaptchaResultData;
|
||||
import com.google.android.gms.safetynet.RemoveHarmfulAppData;
|
||||
import com.google.android.gms.safetynet.SafeBrowsingData;
|
||||
|
||||
interface ISafetyNetCallbacks {
|
||||
oneway void onAttestationResult(in Status status, in AttestationData attestationData) = 0;
|
||||
oneway void onSharedUuid(String s) = 1;
|
||||
oneway void onSafeBrowsingData(in Status status, in SafeBrowsingData safeBrowsingData) = 2;
|
||||
oneway void onVerifyAppsUserResult(in Status status, boolean enabled) = 3;
|
||||
oneway void onHarmfulAppsData(in Status status, in List<HarmfulAppsData> harmfulAppsData) = 4;
|
||||
oneway void onRecaptchaResult(in Status status, in RecaptchaResultData recaptchaResultData) = 5;
|
||||
oneway void onHarmfulAppsInfo(in Status status, in HarmfulAppsInfo harmfulAppsInfo) = 7;
|
||||
oneway void onInitSafeBrowsingResult(in Status status) = 10;
|
||||
oneway void onRemoveHarmfulAppData(in Status status, in RemoveHarmfulAppData removeHarmfulAppData) = 14;
|
||||
}
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
package com.google.android.gms.safetynet.internal;
|
||||
|
||||
import com.google.android.gms.safetynet.internal.ISafetyNetCallbacks;
|
||||
|
||||
interface ISafetyNetService {
|
||||
void attest(ISafetyNetCallbacks callbacks, in byte[] nonce) = 0;
|
||||
void attestWithApiKey(ISafetyNetCallbacks callbacks, in byte[] nonce, String apiKey) = 6;
|
||||
void getSharedUuid(ISafetyNetCallbacks callbacks) = 1;
|
||||
void lookupUri(ISafetyNetCallbacks callbacks, String apiKey, in int[] threatTypes, int version, String uri) = 2;
|
||||
void enableVerifyApps(ISafetyNetCallbacks callbacks) = 3;
|
||||
void listHarmfulApps(ISafetyNetCallbacks callbacks) = 4;
|
||||
void verifyWithRecaptcha(ISafetyNetCallbacks callbacks, String siteKey) = 5;
|
||||
|
||||
// void fun9(ISafetyNetCallbacks callbacks) = 8;
|
||||
// void fun10(ISafetyNetCallbacks callbacks, String s1, int i1, in byte[] b1) = 9;
|
||||
// void fun11(int i1, in Bundle b1) = 10;
|
||||
void initSafeBrowsing(ISafetyNetCallbacks callbacks) = 11;
|
||||
void shutdownSafeBrowsing() = 12;
|
||||
void isVerifyAppsEnabled(ISafetyNetCallbacks callbacks) = 13;
|
||||
//
|
||||
// void fun18(ISafetyNetCallbacks callbacks, int i1, String s1) = 17;
|
||||
// void fun19(ISafetyNetCallbacks callbacks, int i1) = 18;
|
||||
// void removeHarmfulApp(ISafetyNetCallbacks callbacks, String packageName, in byte[] digest) = 19;
|
||||
// void fun21(ISafetyNetCallbacks callbacks, in Bundle b1) = 20;
|
||||
}
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2017 microG Project Team
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package com.google.android.gms.safetynet;
|
||||
|
||||
import org.microg.safeparcel.AutoSafeParcelable;
|
||||
import org.microg.safeparcel.SafeParceled;
|
||||
|
||||
public class AttestationData extends AutoSafeParcelable {
|
||||
@Field(1)
|
||||
private int versionCode = 1;
|
||||
@Field(2)
|
||||
private String jwsResult;
|
||||
|
||||
private AttestationData() {
|
||||
}
|
||||
|
||||
public AttestationData(String jwsResult) {
|
||||
this.jwsResult = jwsResult;
|
||||
}
|
||||
|
||||
public String getJwsResult() {
|
||||
return jwsResult;
|
||||
}
|
||||
|
||||
public static final Creator<AttestationData> CREATOR = new AutoCreator<AttestationData>(AttestationData.class);
|
||||
}
|
||||
|
|
@ -0,0 +1,49 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2021 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.safetynet;
|
||||
|
||||
import org.microg.gms.common.PublicApi;
|
||||
import org.microg.safeparcel.AutoSafeParcelable;
|
||||
|
||||
/**
|
||||
* APK information pertaining to one potentially harmful app.
|
||||
*/
|
||||
@PublicApi
|
||||
public class HarmfulAppsData extends AutoSafeParcelable {
|
||||
/**
|
||||
* The package name of the potentially harmful app.
|
||||
*/
|
||||
@Field(2)
|
||||
public final String apkPackageName;
|
||||
/**
|
||||
* The SHA-256 of the potentially harmful app APK file.
|
||||
*/
|
||||
@Field(3)
|
||||
public final byte[] apkSha256;
|
||||
/**
|
||||
* The potentially harmful app category defined in {@link VerifyAppsConstants}.
|
||||
*/
|
||||
@Field(4)
|
||||
public final int apkCategory;
|
||||
|
||||
private HarmfulAppsData() {
|
||||
apkPackageName = null;
|
||||
apkSha256 = null;
|
||||
apkCategory = 0;
|
||||
}
|
||||
|
||||
@PublicApi(exclude = true)
|
||||
public HarmfulAppsData(String apkPackageName, byte[] apkSha256, int apkCategory) {
|
||||
this.apkPackageName = apkPackageName;
|
||||
this.apkSha256 = apkSha256;
|
||||
this.apkCategory = apkCategory;
|
||||
}
|
||||
|
||||
public static final Creator<HarmfulAppsData> CREATOR = new AutoCreator<HarmfulAppsData>(HarmfulAppsData.class);
|
||||
}
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2017 microG Project Team
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package com.google.android.gms.safetynet;
|
||||
|
||||
import org.microg.safeparcel.AutoSafeParcelable;
|
||||
|
||||
public class HarmfulAppsInfo extends AutoSafeParcelable {
|
||||
@Field(2)
|
||||
public long lastScanTime;
|
||||
@Field(3)
|
||||
public HarmfulAppsData[] harmfulApps = new HarmfulAppsData[0];
|
||||
@Field(4)
|
||||
public int hoursSinceLastScanWithHarmfulApp = -1;
|
||||
@Field(5)
|
||||
public boolean harmfulAppInOtherProfile = false;
|
||||
|
||||
public static final Creator<HarmfulAppsInfo> CREATOR = new AutoCreator<HarmfulAppsInfo>(HarmfulAppsInfo.class);
|
||||
}
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2017 microG Project Team
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package com.google.android.gms.safetynet;
|
||||
|
||||
import org.microg.safeparcel.AutoSafeParcelable;
|
||||
|
||||
public class RecaptchaResultData extends AutoSafeParcelable {
|
||||
@Field(2)
|
||||
public String token;
|
||||
|
||||
public static final Creator<RecaptchaResultData> CREATOR = new AutoCreator<RecaptchaResultData>(RecaptchaResultData.class);
|
||||
}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2017 microG Project Team
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package com.google.android.gms.safetynet;
|
||||
|
||||
import org.microg.safeparcel.AutoSafeParcelable;
|
||||
|
||||
public class RemoveHarmfulAppData extends AutoSafeParcelable {
|
||||
@Field(2)
|
||||
public int field2;
|
||||
@Field(3)
|
||||
public boolean field3;
|
||||
|
||||
public static final Creator<RemoveHarmfulAppData> CREATOR = new AutoCreator<RemoveHarmfulAppData>(RemoveHarmfulAppData.class);
|
||||
}
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2017 microG Project Team
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package com.google.android.gms.safetynet;
|
||||
|
||||
import android.os.ParcelFileDescriptor;
|
||||
|
||||
import com.google.android.gms.common.data.DataHolder;
|
||||
|
||||
import org.microg.safeparcel.AutoSafeParcelable;
|
||||
import org.microg.safeparcel.SafeParceled;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
public class SafeBrowsingData extends AutoSafeParcelable {
|
||||
@Field(1)
|
||||
public int versionCode = 1;
|
||||
@Field(2)
|
||||
public String metadata;
|
||||
@Field(3)
|
||||
public DataHolder data;
|
||||
@Field(4)
|
||||
public ParcelFileDescriptor fileDescriptor;
|
||||
public byte[] fileContents;
|
||||
@Field(5)
|
||||
public long lastUpdateTime;
|
||||
@Field(6)
|
||||
public byte[] state;
|
||||
|
||||
public static final Creator<SafeBrowsingData> CREATOR = new AutoCreator<SafeBrowsingData>(SafeBrowsingData.class);
|
||||
}
|
||||
|
|
@ -0,0 +1,61 @@
|
|||
/*
|
||||
* 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.safetynet;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
|
||||
import com.google.android.gms.common.api.Api;
|
||||
|
||||
import org.microg.gms.common.PublicApi;
|
||||
import org.microg.gms.safetynet.SafetyNetApiImpl;
|
||||
import org.microg.gms.safetynet.SafetyNetGmsClient;
|
||||
|
||||
/**
|
||||
* The SafetyNet API provides access to Google services that help you assess the health and safety of an Android device.
|
||||
* <p>
|
||||
* To use SafetyNet, call {@link #getClient(Context)} or {@link #getClient(Activity)}.
|
||||
*/
|
||||
@PublicApi
|
||||
public class SafetyNet {
|
||||
/**
|
||||
* The API necessary to use SafetyNet.
|
||||
*
|
||||
* @deprecated use {@link #getClient(Context)} or {@link #getClient(Activity)}.
|
||||
*/
|
||||
@Deprecated
|
||||
public static final Api<Api.ApiOptions.NoOptions> API = new Api<>((options, context, looper, clientSettings, callbacks, connectionFailedListener) -> new SafetyNetGmsClient(context, callbacks, connectionFailedListener));;
|
||||
|
||||
/**
|
||||
* The entry point for interacting with the SafetyNet APIs which help assess the health and safety of an Android device.
|
||||
*
|
||||
* @deprecated use {@link #getClient(Context)} or {@link #getClient(Activity)}.
|
||||
*/
|
||||
@Deprecated
|
||||
public static final SafetyNetApi SafetyNetApi = new SafetyNetApiImpl();
|
||||
|
||||
/**
|
||||
* Returns a {@link SafetyNetClient} that is used to access all APIs that are called when the app has a
|
||||
* foreground {@link Activity}.
|
||||
* <p>
|
||||
* Use this method over {@link #getClient(Context)} if your app has a foreground Activity and you will be making
|
||||
* multiple API calls to improve performance.
|
||||
*/
|
||||
public static SafetyNetClient getClient(Activity activity) {
|
||||
return new SafetyNetClient(activity);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a {@link SafetyNetClient} that is used to access all APIs that are called without access to a
|
||||
* foreground {@link Activity}.
|
||||
*/
|
||||
public static SafetyNetClient getClient(Context context) {
|
||||
return new SafetyNetClient(context);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,129 @@
|
|||
/*
|
||||
* 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.safetynet;
|
||||
|
||||
import com.google.android.gms.common.api.GoogleApiClient;
|
||||
import com.google.android.gms.common.api.PendingResult;
|
||||
import com.google.android.gms.common.api.Response;
|
||||
import com.google.android.gms.common.api.Result;
|
||||
import com.google.android.gms.common.api.Status;
|
||||
|
||||
import org.microg.gms.common.PublicApi;
|
||||
|
||||
/**
|
||||
* The main entry point for interacting with SafetyNet.
|
||||
*/
|
||||
public interface SafetyNetApi {
|
||||
/**
|
||||
* Provides user attestation with reCAPTCHA.
|
||||
* <p>
|
||||
* If reCAPTCHA is confident that this is a real user on a real device it will return a token with no challenge.
|
||||
* Otherwise it will provide a visual/audio challenge to attest the humanness of the user before returning a token.
|
||||
* <p>
|
||||
* When you make a request with this API, you must provide your client {@link GoogleApiClient} and site public key
|
||||
* as parameters, and after the request completes, you can get the {@link RecaptchaTokenResult}
|
||||
* from the response.
|
||||
*
|
||||
* @param client The {@link GoogleApiClient} to service the call. The client must be connected using
|
||||
* {@link GoogleApiClient#connect()} before invoking this method.
|
||||
* @param siteKey A site public key registered for this app
|
||||
* @deprecated use {@link SafetyNetClient#verifyWithRecaptcha(String)}
|
||||
*/
|
||||
@Deprecated
|
||||
PendingResult<RecaptchaTokenResult> verifyWithRecaptcha(GoogleApiClient client, String siteKey);
|
||||
|
||||
/**
|
||||
* Response from {@link SafetyNetClient#attest(byte[], String)} that contains a Compatibility Test Suite
|
||||
* attestation result.
|
||||
* <p>
|
||||
* Use the {@link Result#getStatus()} method to obtain a {@link Status} object. Calling the Status object's
|
||||
* {@link Status#isSuccess} indicates whether or not communication with the service was successful, but does not
|
||||
* indicate if the device has passed the compatibility check. If the request was successful,
|
||||
* {@link AttestationResponse#getJwsResult()} may be used to determine whether the device has passed the
|
||||
* compatibility check.
|
||||
*/
|
||||
class AttestationResponse extends Response<AttestationResult> {
|
||||
/**
|
||||
* Gets the JSON Web Signature attestation result.
|
||||
* <p>
|
||||
* Result is in JSON Web Signature format.
|
||||
*
|
||||
* @return JSON Web signature formatted attestation response or {@code null} if an error occurred.
|
||||
*/
|
||||
public String getJwsResult() {
|
||||
return getResult().getJwsResult();
|
||||
}
|
||||
}
|
||||
|
||||
@PublicApi(exclude = true)
|
||||
@Deprecated
|
||||
interface AttestationResult extends Result {
|
||||
String getJwsResult();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link Response} from {@link SafetyNetClient#verifyWithRecaptcha(String)}.
|
||||
* <p>
|
||||
* This Result contains a reCAPTCHA user response token and third party clients should use this token to verify
|
||||
* the user. The token must be validated on the server side to determine whether the user has passed the challenge.
|
||||
*/
|
||||
class RecaptchaTokenResponse extends Response<RecaptchaTokenResult> {
|
||||
/**
|
||||
* Gets the reCAPTCHA user response token which must be validated by calling the siteverify method.
|
||||
*
|
||||
* @return A user response token.
|
||||
*/
|
||||
public String getTokenResult() {
|
||||
return getResult().getTokenResult();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A Result from {@link #verifyWithRecaptcha(GoogleApiClient, String)}.
|
||||
* <p>
|
||||
* This Result contains a reCAPTCHA user response token and third party clients should use this token to verify
|
||||
* the user. Calling the {@link Status#isSuccess()} will indicate whether or not communication with the service was
|
||||
* successful, but does not indicate if the user has passed the reCAPTCHA challenge. The token must be validated on
|
||||
* the server side to determine whether the user has passed the challenge.
|
||||
*
|
||||
* @deprecated use {@link RecaptchaTokenResponse} returned from {@link SafetyNetClient#verifyWithRecaptcha(String)}.
|
||||
*/
|
||||
@Deprecated
|
||||
interface RecaptchaTokenResult extends Result {
|
||||
String getTokenResult();
|
||||
}
|
||||
|
||||
/**
|
||||
* A {@link Response} to get user decisions for the Verify Apps API.
|
||||
*/
|
||||
class VerifyAppsUserResponse extends Response<VerifyAppsUserResult> {
|
||||
/**
|
||||
* Returns whether the user has enabled Verify Apps when prompted.
|
||||
* <p>
|
||||
* This method is only meaningful when used with
|
||||
* {@link SafetyNetClient#enableVerifyApps()} or {@link SafetyNetClient#isVerifyAppsEnabled()}.
|
||||
*/
|
||||
public boolean isVerifyAppsEnabled() {
|
||||
return getResult().isVerifyAppsEnabled();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A {@link Result} to get user decisions for the Verify Apps API.
|
||||
*
|
||||
* @deprecated use {@link VerifyAppsUserResponse} instead.
|
||||
*/
|
||||
@Deprecated
|
||||
interface VerifyAppsUserResult extends Result {
|
||||
/**
|
||||
* Returns whether the user has enabled Verify Apps when prompted.
|
||||
*/
|
||||
boolean isVerifyAppsEnabled();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,180 @@
|
|||
/*
|
||||
* 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.safetynet;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.RemoteException;
|
||||
|
||||
import com.google.android.gms.common.api.Api;
|
||||
import com.google.android.gms.common.api.GoogleApi;
|
||||
import com.google.android.gms.common.api.Status;
|
||||
import com.google.android.gms.safetynet.internal.ISafetyNetCallbacks;
|
||||
import com.google.android.gms.tasks.Task;
|
||||
|
||||
import org.microg.gms.common.PublicApi;
|
||||
import org.microg.gms.common.api.PendingGoogleApiCall;
|
||||
import org.microg.gms.safetynet.ISafetyNetCallbacksDefaultStub;
|
||||
import org.microg.gms.safetynet.SafetyNetGmsClient;
|
||||
|
||||
/**
|
||||
* The main entry point for SafetyNet.
|
||||
*/
|
||||
@PublicApi
|
||||
public class SafetyNetClient extends GoogleApi<Api.ApiOptions.NoOptions> {
|
||||
@PublicApi(exclude = true)
|
||||
SafetyNetClient(Context context) {
|
||||
super(context, SafetyNet.API, Api.ApiOptions.NO_OPTIONS);
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides attestation results for the device.
|
||||
* <p>
|
||||
* An attestation result states whether the device where it is running matches the profile of a device that has
|
||||
* passed Android compatibility testing.
|
||||
* <p>
|
||||
* When you request a compatibility check, you must provide a nonce, which is a random token generated in a
|
||||
* cryptographically secure manner. You can obtain a nonce by generating one within your app each time you make a
|
||||
* compatibility check request. As a more secure option, you can obtain a nonce from your own server, using a
|
||||
* secure connection.
|
||||
* <p>
|
||||
* A nonce used with an attestation request should be at least 16 bytes in length. After you make a request, the
|
||||
* response {@link SafetyNetApi.AttestationResponse} includes your nonce, so you can verify it against the one you
|
||||
* sent. You should only use a nonce value once, for a single request. Use a different nonce for any subsequent
|
||||
* attestation requests.
|
||||
*
|
||||
* @param nonce A cryptographic nonce used for anti-replay and tracking of requests.
|
||||
* @param apiKey An Android API key obtained through the developer console.
|
||||
*/
|
||||
public Task<SafetyNetApi.AttestationResponse> attest(byte[] nonce, String apiKey) {
|
||||
return scheduleTask((PendingGoogleApiCall<SafetyNetApi.AttestationResponse, SafetyNetGmsClient>) (client, completionSource) -> {
|
||||
try {
|
||||
client.attest(new ISafetyNetCallbacksDefaultStub() {
|
||||
@Override
|
||||
public void onAttestationResult(Status status, AttestationData attestationData) throws RemoteException {
|
||||
SafetyNetApi.AttestationResponse response = new SafetyNetApi.AttestationResponse();
|
||||
response.setResult(new SafetyNetApi.AttestationResult() {
|
||||
@Override
|
||||
public String getJwsResult() {
|
||||
return attestationData.getJwsResult();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Status getStatus() {
|
||||
return status;
|
||||
}
|
||||
});
|
||||
completionSource.setResult(response);
|
||||
}
|
||||
}, nonce, apiKey);
|
||||
} catch (Exception e) {
|
||||
completionSource.setException(e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides user attestation with reCAPTCHA.
|
||||
* <p>
|
||||
* If reCAPTCHA is confident that this is a real user on a real device it will return a token with no challenge.
|
||||
* Otherwise it will provide a visual/audio challenge to attest the humanness of the user before returning a token.
|
||||
*
|
||||
* @param siteKey A site public key registered for this app
|
||||
*/
|
||||
public Task<SafetyNetApi.RecaptchaTokenResponse> verifyWithRecaptcha(String siteKey) {
|
||||
return scheduleTask((PendingGoogleApiCall<SafetyNetApi.RecaptchaTokenResponse, SafetyNetGmsClient>) (client, completionSource) -> {
|
||||
try {
|
||||
client.verifyWithRecaptcha(new ISafetyNetCallbacksDefaultStub() {
|
||||
@Override
|
||||
public void onRecaptchaResult(Status status, RecaptchaResultData recaptchaResultData) throws RemoteException {
|
||||
SafetyNetApi.RecaptchaTokenResponse response = new SafetyNetApi.RecaptchaTokenResponse();
|
||||
response.setResult(new SafetyNetApi.RecaptchaTokenResult() {
|
||||
@Override
|
||||
public String getTokenResult() {
|
||||
if (recaptchaResultData != null) {
|
||||
return recaptchaResultData.token;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Status getStatus() {
|
||||
return status;
|
||||
}
|
||||
});
|
||||
completionSource.setResult(response);
|
||||
|
||||
}
|
||||
}, siteKey);
|
||||
} catch (Exception e) {
|
||||
completionSource.setException(e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Prompts the user to enable Verify Apps if it is currently turned off.
|
||||
*/
|
||||
public Task<SafetyNetApi.VerifyAppsUserResponse> enableVerifyApps() {
|
||||
return scheduleTask((PendingGoogleApiCall<SafetyNetApi.VerifyAppsUserResponse, SafetyNetGmsClient>) (client, completionSource) -> {
|
||||
try {
|
||||
client.enableVerifyApps(new ISafetyNetCallbacksDefaultStub() {
|
||||
@Override
|
||||
public void onVerifyAppsUserResult(Status status, boolean enabled) throws RemoteException {
|
||||
SafetyNetApi.VerifyAppsUserResponse response = new SafetyNetApi.VerifyAppsUserResponse();
|
||||
response.setResult(new SafetyNetApi.VerifyAppsUserResult() {
|
||||
@Override
|
||||
public boolean isVerifyAppsEnabled() {
|
||||
return enabled;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Status getStatus() {
|
||||
return status;
|
||||
}
|
||||
});
|
||||
completionSource.setResult(response);
|
||||
}
|
||||
});
|
||||
} catch (Exception e) {
|
||||
completionSource.setException(e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether Verify Apps is enabled.
|
||||
*/
|
||||
public Task<SafetyNetApi.VerifyAppsUserResponse> isVerifyAppsEnabled() {
|
||||
return scheduleTask((PendingGoogleApiCall<SafetyNetApi.VerifyAppsUserResponse, SafetyNetGmsClient>) (client, completionSource) -> {
|
||||
try {
|
||||
client.isVerifyAppsEnabled(new ISafetyNetCallbacksDefaultStub() {
|
||||
@Override
|
||||
public void onVerifyAppsUserResult(Status status, boolean enabled) throws RemoteException {
|
||||
SafetyNetApi.VerifyAppsUserResponse response = new SafetyNetApi.VerifyAppsUserResponse();
|
||||
response.setResult(new SafetyNetApi.VerifyAppsUserResult() {
|
||||
@Override
|
||||
public boolean isVerifyAppsEnabled() {
|
||||
return enabled;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Status getStatus() {
|
||||
return status;
|
||||
}
|
||||
});
|
||||
completionSource.setResult(response);
|
||||
}
|
||||
});
|
||||
} catch (Exception e) {
|
||||
completionSource.setException(e);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,71 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2021 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.safetynet;
|
||||
|
||||
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.
|
||||
*/
|
||||
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;
|
||||
}
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2021 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.safetynet;
|
||||
|
||||
import org.microg.gms.common.PublicApi;
|
||||
|
||||
/**
|
||||
* Constants pertaining to the Verify Apps SafetyNet API.
|
||||
*/
|
||||
@PublicApi
|
||||
public class VerifyAppsConstants {
|
||||
/**
|
||||
* An action that is broadcasted when harmful apps are discovered.
|
||||
*/
|
||||
public static final String ACTION_HARMFUL_APPS_FOUND = "com.google.android.gms.safetynet.action.HARMFUL_APPS_FOUND";
|
||||
/**
|
||||
* An action that is broadcasted when a harmful app is blocked from installation.
|
||||
*/
|
||||
public static final String ACTION_HARMFUL_APP_BLOCKED = "com.google.android.gms.safetynet.action.HARMFUL_APP_BLOCKED";
|
||||
/**
|
||||
* An action that is broadcasted when a harmful app is installed.
|
||||
*/
|
||||
public static final String ACTION_HARMFUL_APP_INSTALLED = "com.google.android.gms.safetynet.action.HARMFUL_APP_INSTALLED";
|
||||
|
||||
public static final int HARMFUL_CATEGORY_RANSOMWARE = 1;
|
||||
public static final int HARMFUL_CATEGORY_PHISHING = 2;
|
||||
public static final int HARMFUL_CATEGORY_TROJAN = 3;
|
||||
public static final int HARMFUL_CATEGORY_UNCOMMON = 4;
|
||||
public static final int HARMFUL_CATEGORY_FRAUDWARE = 5;
|
||||
public static final int HARMFUL_CATEGORY_TOLL_FRAUD = 6;
|
||||
public static final int HARMFUL_CATEGORY_WAP_FRAUD = 7;
|
||||
public static final int HARMFUL_CATEGORY_CALL_FRAUD = 8;
|
||||
public static final int HARMFUL_CATEGORY_BACKDOOR = 9;
|
||||
public static final int HARMFUL_CATEGORY_SPYWARE = 10;
|
||||
public static final int HARMFUL_CATEGORY_GENERIC_MALWARE = 11;
|
||||
public static final int HARMFUL_CATEGORY_HARMFUL_SITE = 12;
|
||||
public static final int HARMFUL_CATEGORY_WINDOWS_MALWARE = 13;
|
||||
public static final int HARMFUL_CATEGORY_HOSTILE_DOWNLOADER = 14;
|
||||
public static final int HARMFUL_CATEGORY_NON_ANDROID_THREAT = 15;
|
||||
public static final int HARMFUL_CATEGORY_ROOTING = 16;
|
||||
public static final int HARMFUL_CATEGORY_PRIVILEGE_ESCALATION = 17;
|
||||
public static final int HARMFUL_CATEGORY_TRACKING = 18;
|
||||
public static final int HARMFUL_CATEGORY_SPAM = 19;
|
||||
public static final int HARMFUL_CATEGORY_DENIAL_OF_SERVICE = 20;
|
||||
public static final int HARMFUL_CATEGORY_DATA_COLLECTION = 21;
|
||||
}
|
||||
|
|
@ -0,0 +1,58 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2022 microG Project Team
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package org.microg.gms.safetynet;
|
||||
|
||||
import android.os.RemoteException;
|
||||
|
||||
import com.google.android.gms.common.api.Status;
|
||||
import com.google.android.gms.safetynet.AttestationData;
|
||||
import com.google.android.gms.safetynet.HarmfulAppsData;
|
||||
import com.google.android.gms.safetynet.HarmfulAppsInfo;
|
||||
import com.google.android.gms.safetynet.RecaptchaResultData;
|
||||
import com.google.android.gms.safetynet.RemoveHarmfulAppData;
|
||||
import com.google.android.gms.safetynet.SafeBrowsingData;
|
||||
import com.google.android.gms.safetynet.internal.ISafetyNetCallbacks;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class ISafetyNetCallbacksDefaultStub extends ISafetyNetCallbacks.Stub {
|
||||
@Override
|
||||
public void onAttestationResult(Status status, AttestationData attestationData) throws RemoteException {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSharedUuid(String s) throws RemoteException {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSafeBrowsingData(Status status, SafeBrowsingData safeBrowsingData) throws RemoteException {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onVerifyAppsUserResult(Status status, boolean enabled) throws RemoteException {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onHarmfulAppsData(Status status, List<HarmfulAppsData> harmfulAppsData) throws RemoteException {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRecaptchaResult(Status status, RecaptchaResultData recaptchaResultData) throws RemoteException {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onHarmfulAppsInfo(Status status, HarmfulAppsInfo harmfulAppsInfo) throws RemoteException {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onInitSafeBrowsingResult(Status status) throws RemoteException {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRemoveHarmfulAppData(Status status, RemoveHarmfulAppData removeHarmfulAppData) throws RemoteException {
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2022 microG Project Team
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package org.microg.gms.safetynet;
|
||||
|
||||
import android.os.RemoteException;
|
||||
|
||||
import com.google.android.gms.common.api.GoogleApiClient;
|
||||
import com.google.android.gms.common.api.PendingResult;
|
||||
import com.google.android.gms.common.api.Status;
|
||||
import com.google.android.gms.safetynet.RecaptchaResultData;
|
||||
import com.google.android.gms.safetynet.SafetyNet;
|
||||
import com.google.android.gms.safetynet.SafetyNetApi;
|
||||
import com.google.android.gms.safetynet.internal.ISafetyNetCallbacks;
|
||||
|
||||
import org.microg.gms.common.GmsConnector;
|
||||
|
||||
public class SafetyNetApiImpl implements SafetyNetApi {
|
||||
@Override
|
||||
public PendingResult<RecaptchaTokenResult> verifyWithRecaptcha(GoogleApiClient apiClient, String siteKey) {
|
||||
return GmsConnector.call(apiClient, SafetyNet.API, (GmsConnector.Callback<SafetyNetGmsClient, RecaptchaTokenResult>) (client, resultProvider) -> client.verifyWithRecaptcha(new ISafetyNetCallbacksDefaultStub() {
|
||||
@Override
|
||||
public void onRecaptchaResult(Status status, RecaptchaResultData recaptchaResultData) throws RemoteException {
|
||||
resultProvider.onResultAvailable(new RecaptchaTokenResult() {
|
||||
@Override
|
||||
public String getTokenResult() {
|
||||
return recaptchaResultData.token;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Status getStatus() {
|
||||
return status;
|
||||
}
|
||||
});
|
||||
}
|
||||
}, siteKey));
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2022 microG Project Team
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package org.microg.gms.safetynet;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.IBinder;
|
||||
import android.os.RemoteException;
|
||||
|
||||
import com.google.android.gms.safetynet.internal.ISafetyNetCallbacks;
|
||||
import com.google.android.gms.safetynet.internal.ISafetyNetService;
|
||||
|
||||
import org.microg.gms.common.GmsClient;
|
||||
import org.microg.gms.common.GmsService;
|
||||
import com.google.android.gms.common.api.internal.ConnectionCallbacks;
|
||||
import com.google.android.gms.common.api.internal.OnConnectionFailedListener;
|
||||
|
||||
public class SafetyNetGmsClient extends GmsClient<ISafetyNetService> {
|
||||
public SafetyNetGmsClient(Context context, ConnectionCallbacks callbacks, OnConnectionFailedListener connectionFailedListener) {
|
||||
super(context, callbacks, connectionFailedListener, GmsService.SAFETY_NET_CLIENT.ACTION);
|
||||
serviceId = GmsService.SAFETY_NET_CLIENT.SERVICE_ID;
|
||||
}
|
||||
|
||||
public void attest(ISafetyNetCallbacks callbacks, byte[] nonce, String apiKey) throws RemoteException {
|
||||
getServiceInterface().attestWithApiKey(callbacks, nonce, apiKey);
|
||||
}
|
||||
|
||||
public void verifyWithRecaptcha(ISafetyNetCallbacks callbacks, String siteKey) throws RemoteException {
|
||||
getServiceInterface().verifyWithRecaptcha(callbacks, siteKey);
|
||||
}
|
||||
|
||||
public void enableVerifyApps(ISafetyNetCallbacks callbacks) throws RemoteException {
|
||||
getServiceInterface().enableVerifyApps(callbacks);
|
||||
}
|
||||
|
||||
public void isVerifyAppsEnabled(ISafetyNetCallbacks callbacks) throws RemoteException {
|
||||
getServiceInterface().isVerifyAppsEnabled(callbacks);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ISafetyNetService interfaceFromBinder(IBinder binder) {
|
||||
return ISafetyNetService.Stub.asInterface(binder);
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue