mirror of
https://github.com/TeamVanced/VancedMicroG
synced 2024-12-05 02:12:54 +01:00
SafetyNet updates
- Add more API details - preliminary support for SafetyNet reCAPTCHA - prepare for improved DroidGuard handling
This commit is contained in:
parent
7f131c0cfa
commit
1a809e0e47
@ -22,6 +22,7 @@ buildscript {
|
||||
ext.navigationVersion = '2.3.1'
|
||||
ext.preferenceVersion = '1.1.1'
|
||||
ext.recyclerviewVersion = '1.1.0'
|
||||
ext.webkitVersion = '1.4.0'
|
||||
|
||||
ext.supportLibraryVersion = '28.0.0'
|
||||
ext.slf4jVersion = '1.7.25'
|
||||
|
@ -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;
|
@ -3,6 +3,9 @@ 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 {
|
||||
@ -11,4 +14,7 @@ interface ISafetyNetCallbacks {
|
||||
void onSafeBrowsingData(in Status status, in SafeBrowsingData safeBrowsingData) = 2;
|
||||
void onBoolean(in Status status, boolean b) = 3;
|
||||
void onHarmfulAppsData(in Status status, in List<HarmfulAppsData> harmfulAppsData) = 4;
|
||||
}
|
||||
void onRecaptchaResult(in Status status, in RecaptchaResultData recaptchaResultData) = 5;
|
||||
void onHarmfulAppsInfo(in Status status, in HarmfulAppsInfo harmfulAppsInfo) = 7;
|
||||
void onRemoveHarmfulAppData(in Status status, in RemoveHarmfulAppData removeHarmfulAppData) = 14;
|
||||
}
|
||||
|
@ -9,4 +9,5 @@ interface ISafetyNetService {
|
||||
void lookupUri(ISafetyNetCallbacks callbacks, String s1, in int[] threatTypes, int i, String s2) = 2;
|
||||
void init(ISafetyNetCallbacks callbacks) = 3;
|
||||
void getHarmfulAppsList(ISafetyNetCallbacks callbacks) = 4;
|
||||
}
|
||||
void verifyWithRecaptcha(ISafetyNetCallbacks callbacks, String siteKey) = 5;
|
||||
}
|
||||
|
@ -1,23 +1,49 @@
|
||||
/*
|
||||
* Copyright (C) 2013-2017 microG Project Team
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
* 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,32 @@
|
||||
/*
|
||||
* Copyright (C) 2013-2017 microG Project Team
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.google.android.gms.safetynet;
|
||||
|
||||
import org.microg.safeparcel.AutoSafeParcelable;
|
||||
|
||||
public class HarmfulAppsInfo extends AutoSafeParcelable {
|
||||
@Field(2)
|
||||
public long field2;
|
||||
@Field(3)
|
||||
public HarmfulAppsData[] data;
|
||||
@Field(4)
|
||||
public int field4;
|
||||
@Field(5)
|
||||
public boolean field5;
|
||||
|
||||
public static final Creator<HarmfulAppsInfo> CREATOR = new AutoCreator<HarmfulAppsInfo>(HarmfulAppsInfo.class);
|
||||
}
|
@ -0,0 +1,26 @@
|
||||
/*
|
||||
* Copyright (C) 2013-2017 microG Project Team
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
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,28 @@
|
||||
/*
|
||||
* Copyright (C) 2013-2017 microG Project Team
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
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);
|
||||
}
|
@ -16,18 +16,30 @@
|
||||
|
||||
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 {
|
||||
@SafeParceled(1)
|
||||
private int versionCode = 1;
|
||||
@SafeParceled(2)
|
||||
private String status;
|
||||
@SafeParceled(3)
|
||||
private DataHolder data;
|
||||
@Field(1)
|
||||
public int versionCode = 1;
|
||||
@Field(2)
|
||||
public String status;
|
||||
@Field(3)
|
||||
public DataHolder data;
|
||||
@Field(4)
|
||||
public ParcelFileDescriptor fileDescriptor;
|
||||
public File file;
|
||||
public byte[] fileContents;
|
||||
@Field(5)
|
||||
public long field5;
|
||||
@Field(6)
|
||||
public byte[] field6;
|
||||
|
||||
public static final Creator<SafeBrowsingData> CREATOR = new AutoCreator<SafeBrowsingData>(SafeBrowsingData.class);
|
||||
}
|
||||
|
@ -0,0 +1,37 @@
|
||||
/*
|
||||
* 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 {
|
||||
public static final int SAFE_BROWSING_UNSUPPORTED_THREAT_TYPES = 12000;
|
||||
public static final int SAFE_BROWSING_MISSING_API_KEYINT = 12001;
|
||||
public static final int SAFE_BROWSING_API_NOT_AVAILABLE = 12002;
|
||||
public static final int VERIFY_APPS_NOT_AVAILABLE = 12003;
|
||||
public static final int VERIFY_APPS_INTERNAL_ERROR = 12004;
|
||||
public static final int VERIFY_APPS_NOT_ENABLED = 12005;
|
||||
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.
|
||||
*/
|
||||
public static final int RECAPTCHA_INVALID_KEYTYPE = 12008;
|
||||
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.
|
||||
*/
|
||||
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;
|
||||
}
|
@ -6,6 +6,11 @@
|
||||
|
||||
<resources>
|
||||
|
||||
<style name="Theme.AppCompat.Light.Dialog.NoActionBar">
|
||||
<item name="windowActionBar">false</item>
|
||||
<item name="windowNoTitle">true</item>
|
||||
</style>
|
||||
|
||||
<style name="Theme.AppCompat.Light.Dialog.Alert.NoActionBar">
|
||||
<item name="windowActionBar">false</item>
|
||||
<item name="windowNoTitle">true</item>
|
||||
|
@ -90,18 +90,58 @@ public enum GmsService {
|
||||
TARGET_DEVICE(76, "com.google.android.gms.smartdevice.d2d.TargetDeviceService.START"),
|
||||
APP_INVITE(77, "com.google.android.gms.appinvite.service.START"),
|
||||
TAP_AND_PAY(79, "com.google.android.gms.tapandpay.service.BIND"),
|
||||
CHROME_SYNC(80, "com.google.android.gms.chromesync.service.START"),
|
||||
ACCOUNTS(81, "com.google.android.gms.smartdevice.setup.accounts.AccountsService.START"),
|
||||
CAST_REMOTE_DISPLAY(83, "com.google.android.gms.cast.remote_display.service.START"),
|
||||
TRUST_AGENT(85, "com.google.android.gms.trustagent.StateApi.START"),
|
||||
AUTH_SIGN_IN(91, "com.google.android.gms.auth.api.signin.service.START"),
|
||||
MEASUREMENT(93, "com.google.android.gms.measurement.START"),
|
||||
FREIGHTER(98, "com.google.android.gms.freighter.service.START"),
|
||||
GUNS(110, "com.google.android.gms.notifications.service.START"),
|
||||
BLE(111, "com.google.android.gms.beacon.internal.IBleService.START"),
|
||||
FIREBASE_AUTH(112, "com.google.firebase.auth.api.gms.service.START"),
|
||||
APP_INDEXING(113),
|
||||
GASS(116, "com.google.android.gms.gass.START"),
|
||||
WORK_ACCOUNT(120),
|
||||
CAST_FIRSTPATY(122, "com.google.android.gms.cast.firstparty.START"),
|
||||
AD_CACHE(123, "com.google.android.gms.ads.service.CACHE"),
|
||||
DYNAMIC_LINKS(131, "com.google.firebase.dynamiclinks.service.START"),
|
||||
ROMANESCO(135, "com.google.android.gms.romanesco.service.START"),
|
||||
TRAINER(139, "com.google.android.gms.learning.trainer.START"),
|
||||
FIDO2_REGULAR(148, "com.google.android.gms.fido.fido2.regular.START"),
|
||||
FIDO2_PRIVILEGED(149, "com.google.android.gms.fido.fido2.privileged.START"),
|
||||
DATA_DOWNLOAD(152, "com.google.android.mdd.service.START"),
|
||||
ACCOUNT_DATA(153, "com.google.android.gms.auth.account.data.service.START"),
|
||||
CONSTELLATION(155, "com.google.android.gms.constellation.service.START"),
|
||||
AUDIT(154, "com.google.android.gms.audit.service.START"),
|
||||
SYSTEM_UPDATE(157, "com.google.android.gms.update.START_API_SERVICE"),
|
||||
USER_LOCATION(163, "com.google.android.gms.userlocation.service.START"),
|
||||
LANGUAGE_PROFILE(167, "com.google.android.gms.languageprofile.service.START"),
|
||||
MDNS(168, "com.google.android.gms.mdns.service.START"),
|
||||
FIDO2_ZEROPARTY(180, "com.google.android.gms.fido.fido2.zeroparty.START"),
|
||||
G1_RESTORE(181, "com.google.android.gms.backup.G1_RESTORE"),
|
||||
G1_BACKUP(182, "com.google.android.gms.backup.G1_BACKUP"),
|
||||
CARRIER_AUTH(191, "com.google.android.gms.carrierauth.service.START"),
|
||||
SYSTEM_UPDATE_SINGLE_UESR(192, "com.google.android.gms.update.START_SINGLE_USER_API_SERVICE"),
|
||||
APP_USAGE(193, "com.google.android.gms.appusage.service.START"),
|
||||
PHONE_INTERNAL(197, "com.google.android.gms.auth.api.phone.service.InternalService.START"),
|
||||
PAY(198, "com.google.android.gms.pay.service.BIND"),
|
||||
ASTERISM(199, "com.google.android.gms.asterism.service.START"),
|
||||
MODULE_RESTORE(201, "com.google.android.gms.backup.GMS_MODULE_RESTORE"),
|
||||
FACS_CACHE(202, "com.google.android.gms.facs.cache.service.START"),
|
||||
RECAPTCHA(205, "com.google.android.gms.recaptcha.service.START"),
|
||||
CONTACT_SYNC(208, "com.google.android.gms.people.contactssync.service.START"),
|
||||
IDENTITY_SIGN_IN(212, "com.google.android.gms.auth.api.identity.service.signin.START"),
|
||||
CREDENTIAL_STORE(214, "com.google.android.gms.fido.credentialstore.internal_service.START"),
|
||||
EVENT_ATTESTATION(216, "com.google.android.gms.ads.identifier.service.EVENT_ATTESTATION"),
|
||||
SCHEDULER(218, "com.google.android.gms.scheduler.ACTION_PROXY_SCHEDULE"),
|
||||
AUTHORIZATION(219, "com.google.android.gms.auth.api.identity.service.authorization.START"),
|
||||
FACS_SYNC(220, "com.google.android.gms.facs.internal.service.START"),
|
||||
CONFIG_SYNC(221, "com.google.android.gms.auth.config.service.START"),
|
||||
CREDENTIAL_SAVING(223, "com.google.android.gms.auth.api.identity.service.credentialsaving.START"),
|
||||
GOOGLE_AUTH(224, "com.google.android.gms.auth.account.authapi.START"),
|
||||
ENTERPRISE_LOADER(225, "com.google.android.gms.enterprise.loader.service.START"),
|
||||
THUNDERBIRD(226, "com.google.android.gms.thunderbird.service.START"),
|
||||
NEARBY_EXPOSURE(236, "com.google.android.gms.nearby.exposurenotification.START"),
|
||||
;
|
||||
|
||||
|
@ -64,11 +64,14 @@ dependencies {
|
||||
implementation "androidx.appcompat:appcompat:$appcompatVersion"
|
||||
implementation "androidx.mediarouter:mediarouter:$mediarouterVersion"
|
||||
implementation "androidx.preference:preference-ktx:$preferenceVersion"
|
||||
implementation "androidx.webkit:webkit:$webkitVersion"
|
||||
|
||||
// Navigation
|
||||
implementation "androidx.navigation:navigation-fragment-ktx:$navigationVersion"
|
||||
implementation "androidx.navigation:navigation-ui-ktx:$navigationVersion"
|
||||
|
||||
implementation "com.android.volley:volley:$volleyVersion"
|
||||
|
||||
implementation "androidx.lifecycle:lifecycle-service:$lifecycleVersion"
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlinVersion"
|
||||
}
|
||||
|
@ -294,7 +294,7 @@
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
|
||||
<!-- DroidGuard -->
|
||||
<!-- DroidGuard / SafetyNet / reCAPTCHA -->
|
||||
|
||||
<service android:name="org.microg.gms.droidguard.DroidGuardService">
|
||||
<intent-filter>
|
||||
@ -304,6 +304,19 @@
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
</intent-filter>
|
||||
</service>
|
||||
<receiver android:name="org.microg.gms.droidguard.ServiceInfoReceiver" />
|
||||
|
||||
<service android:name="org.microg.gms.safetynet.SafetyNetClientService">
|
||||
<intent-filter>
|
||||
<action android:name="com.google.android.gms.safetynet.service.START" />
|
||||
</intent-filter>
|
||||
</service>
|
||||
<receiver android:name="org.microg.gms.safetynet.ServiceInfoReceiver" />
|
||||
|
||||
<!-- TODO: Should be in :ui process and contact DroidGuardService instead of directly invoking droidguard -->
|
||||
<activity
|
||||
android:name="org.microg.gms.recaptcha.ReCaptchaActivity"
|
||||
android:theme="@style/Theme.AppCompat.Light.Dialog.NoActionBar" />
|
||||
|
||||
<!-- Car -->
|
||||
|
||||
@ -657,13 +670,6 @@
|
||||
</intent-filter>
|
||||
</service>
|
||||
|
||||
<receiver android:name="org.microg.gms.snet.ServiceInfoReceiver" />
|
||||
|
||||
<service android:name="org.microg.gms.snet.SafetyNetClientService">
|
||||
<intent-filter>
|
||||
<action android:name="com.google.android.gms.safetynet.service.START" />
|
||||
</intent-filter>
|
||||
</service>
|
||||
<service android:name="org.microg.gms.wallet.PaymentService">
|
||||
<intent-filter>
|
||||
<action android:name="com.google.android.gms.wallet.service.BIND" />
|
||||
|
@ -1,20 +1,9 @@
|
||||
/*
|
||||
* Copyright (C) 2013-2017 microG Project Team
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
* SPDX-FileCopyrightText: 2021, microG Project Team
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package org.microg.gms.snet;
|
||||
package org.microg.gms.safetynet;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.Context;
|
||||
@ -24,11 +13,15 @@ import android.content.pm.Signature;
|
||||
import android.util.Base64;
|
||||
import android.util.Log;
|
||||
|
||||
import com.squareup.wire.Wire;
|
||||
|
||||
import org.microg.gms.common.Build;
|
||||
import org.microg.gms.common.Constants;
|
||||
import org.microg.gms.common.PackageUtils;
|
||||
import org.microg.gms.common.Utils;
|
||||
import org.microg.gms.snet.AttestRequest;
|
||||
import org.microg.gms.snet.AttestResponse;
|
||||
import org.microg.gms.snet.FileState;
|
||||
import org.microg.gms.snet.SELinuxState;
|
||||
import org.microg.gms.snet.SafetyNetData;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.File;
|
||||
@ -81,6 +74,7 @@ public class Attestation {
|
||||
.seLinuxState(new SELinuxState.Builder().enabled(true).supported(true).build())
|
||||
.suCandidates(Collections.<FileState>emptyList())
|
||||
.build();
|
||||
Log.d(TAG, "Payload: "+payload.toString());
|
||||
return this.payload = payload.encode();
|
||||
}
|
||||
|
||||
@ -108,29 +102,32 @@ public class Attestation {
|
||||
|
||||
private ByteString getPackageFileDigest() {
|
||||
try {
|
||||
FileInputStream is = new FileInputStream(new File(context.getPackageManager().getApplicationInfo(packageName, 0).sourceDir));
|
||||
MessageDigest digest = getSha256Digest();
|
||||
byte[] data = new byte[16384];
|
||||
while (true) {
|
||||
int read = is.read(data);
|
||||
if (read < 0) break;
|
||||
digest.update(data, 0, read);
|
||||
}
|
||||
return ByteString.of(digest.digest());
|
||||
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 {
|
||||
PackageInfo pi = context.getPackageManager().getPackageInfo(packageName, PackageManager.GET_SIGNATURES);
|
||||
ArrayList<ByteString> res = new ArrayList<>();
|
||||
MessageDigest digest = getSha256Digest();
|
||||
for (Signature signature : pi.signatures) {
|
||||
res.add(ByteString.of(digest.digest(signature.toByteArray())));
|
||||
for (byte[] bytes : getPackageSignatures(context, packageName)) {
|
||||
res.add(ByteString.of(bytes));
|
||||
}
|
||||
return res;
|
||||
} catch (Exception e) {
|
||||
@ -139,6 +136,16 @@ public class Attestation {
|
||||
}
|
||||
}
|
||||
|
||||
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");
|
||||
@ -154,6 +161,8 @@ public class Attestation {
|
||||
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));
|
||||
Build build = Utils.getBuild(context);
|
||||
connection.setRequestProperty("User-Agent", "SafetyNet/" + Constants.GMS_VERSION_CODE + " (" + build.device + " " + build.id + "); gzip");
|
||||
|
@ -1,25 +1,13 @@
|
||||
/*
|
||||
* Copyright (C) 2017 microG Project Team
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
* SPDX-FileCopyrightText: 2021, microG Project Team
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package org.microg.gms.snet;
|
||||
package org.microg.gms.safetynet;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.util.Log;
|
||||
|
||||
import org.microg.gms.common.PackageUtils;
|
||||
|
@ -1,37 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2013-2017 microG Project Team
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.microg.gms.snet;
|
||||
|
||||
import android.os.RemoteException;
|
||||
|
||||
import com.google.android.gms.common.internal.GetServiceRequest;
|
||||
import com.google.android.gms.common.internal.IGmsCallbacks;
|
||||
|
||||
import org.microg.gms.BaseService;
|
||||
import org.microg.gms.common.GmsService;
|
||||
|
||||
public class SafetyNetClientService extends BaseService {
|
||||
|
||||
public SafetyNetClientService() {
|
||||
super("GmsSafetyNetClientSvc", GmsService.SAFETY_NET_CLIENT);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleServiceRequest(IGmsCallbacks callback, GetServiceRequest request, GmsService service) throws RemoteException {
|
||||
callback.onPostInitComplete(0, new SafetyNetClientServiceImpl(this, request.packageName), null);
|
||||
}
|
||||
}
|
@ -1,137 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2013-2017 microG Project Team
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.microg.gms.snet;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
import android.os.Parcel;
|
||||
import android.os.RemoteException;
|
||||
import android.util.Base64;
|
||||
import android.util.Log;
|
||||
|
||||
import com.google.android.gms.common.api.CommonStatusCodes;
|
||||
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.internal.ISafetyNetCallbacks;
|
||||
import com.google.android.gms.safetynet.internal.ISafetyNetService;
|
||||
|
||||
import org.microg.gms.checkin.LastCheckinInfo;
|
||||
import org.microg.gms.common.PackageUtils;
|
||||
import org.microg.gms.droidguard.RemoteDroidGuardConnector;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
|
||||
public class SafetyNetClientServiceImpl extends ISafetyNetService.Stub {
|
||||
private static final String TAG = "GmsSafetyNetClientImpl";
|
||||
private static final String DEFAULT_API_KEY = "AIzaSyDqVnJBjE5ymo--oBJt3On7HQx9xNm1RHA";
|
||||
|
||||
private Context context;
|
||||
private String packageName;
|
||||
private Attestation attestation;
|
||||
|
||||
public SafetyNetClientServiceImpl(Context context, String packageName) {
|
||||
this.context = context;
|
||||
this.packageName = packageName;
|
||||
this.attestation = new Attestation(context, packageName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void attest(ISafetyNetCallbacks callbacks, byte[] nonce) throws RemoteException {
|
||||
attestWithApiKey(callbacks, nonce, DEFAULT_API_KEY);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void attestWithApiKey(final ISafetyNetCallbacks callbacks, final byte[] nonce, String apiKey) throws RemoteException {
|
||||
if (nonce == null) {
|
||||
callbacks.onAttestationData(new Status(CommonStatusCodes.DEVELOPER_ERROR), null);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!SafetyNetPrefs.get(context).isEnabled()) {
|
||||
Log.d(TAG, "ignoring SafetyNet request, it's disabled");
|
||||
callbacks.onAttestationData(Status.CANCELED, null);
|
||||
return;
|
||||
}
|
||||
|
||||
new Thread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
try {
|
||||
attestation.buildPayload(nonce);
|
||||
RemoteDroidGuardConnector conn = new RemoteDroidGuardConnector(context);
|
||||
Bundle bundle = new Bundle();
|
||||
bundle.putString("contentBinding", attestation.getPayloadHashBase64());
|
||||
RemoteDroidGuardConnector.Result dg = conn.guard("attest", Long.toString(LastCheckinInfo.read(context).androidId), bundle);
|
||||
if (!SafetyNetPrefs.get(context).isOfficial() || dg != null && dg.getStatusCode() == 0 && dg.getResult() != null) {
|
||||
Log.d(TAG, dg == null ? "No dg result" : ("Status: " + dg.getStatusCode() + ", error:" + dg.getErrorMsg()));
|
||||
if (dg != null && dg.getStatusCode() == 0 && dg.getResult() != null) {
|
||||
attestation.setDroidGaurdResult(Base64.encodeToString(dg.getResult(), Base64.NO_WRAP + Base64.NO_PADDING + Base64.URL_SAFE));
|
||||
}
|
||||
AttestationData data = new AttestationData(attestation.attest(apiKey));
|
||||
callbacks.onAttestationData(Status.SUCCESS, data);
|
||||
} else {
|
||||
callbacks.onAttestationData(dg == null ? Status.INTERNAL_ERROR : new Status(dg.getStatusCode()), null);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
Log.w(TAG, e);
|
||||
callbacks.onAttestationData(Status.INTERNAL_ERROR, null);
|
||||
}
|
||||
} catch (RemoteException e) {
|
||||
Log.w(TAG, e);
|
||||
}
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void getSharedUuid(ISafetyNetCallbacks callbacks) throws RemoteException {
|
||||
PackageUtils.checkPackageUid(context, packageName, getCallingUid());
|
||||
PackageUtils.assertExtendedAccess(context);
|
||||
|
||||
// TODO
|
||||
Log.d(TAG, "dummy Method: getSharedUuid");
|
||||
callbacks.onString("aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void lookupUri(ISafetyNetCallbacks callbacks, String s1, int[] threatTypes, int i, String s2) throws RemoteException {
|
||||
Log.d(TAG, "unimplemented Method: lookupUri");
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(ISafetyNetCallbacks callbacks) throws RemoteException {
|
||||
Log.d(TAG, "dummy Method: init");
|
||||
callbacks.onBoolean(Status.SUCCESS, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void getHarmfulAppsList(ISafetyNetCallbacks callbacks) throws RemoteException {
|
||||
Log.d(TAG, "dummy Method: unknown4");
|
||||
callbacks.onHarmfulAppsData(Status.SUCCESS, new ArrayList<HarmfulAppsData>());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException {
|
||||
if (super.onTransact(code, data, reply, flags)) return true;
|
||||
Log.d(TAG, "onTransact [unknown]: " + code + ", " + data + ", " + flags);
|
||||
return false;
|
||||
}
|
||||
}
|
@ -28,9 +28,9 @@ import org.microg.tools.ui.AbstractSettingsActivity;
|
||||
import org.microg.tools.ui.RadioButtonPreference;
|
||||
import org.microg.tools.ui.ResourceSettingsFragment;
|
||||
|
||||
import static org.microg.gms.snet.SafetyNetPrefs.PREF_SNET_OFFICIAL;
|
||||
import static org.microg.gms.snet.SafetyNetPrefs.PREF_SNET_SELF_SIGNED;
|
||||
import static org.microg.gms.snet.SafetyNetPrefs.PREF_SNET_THIRD_PARTY;
|
||||
import static org.microg.gms.safetynet.SafetyNetPrefs.PREF_SNET_OFFICIAL;
|
||||
import static org.microg.gms.safetynet.SafetyNetPrefs.PREF_SNET_SELF_SIGNED;
|
||||
import static org.microg.gms.safetynet.SafetyNetPrefs.PREF_SNET_THIRD_PARTY;
|
||||
|
||||
public class SafetyNetAdvancedFragment extends ResourceSettingsFragment {
|
||||
|
||||
|
@ -0,0 +1,70 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2021, microG Project Team
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package org.microg.gms.droidguard
|
||||
|
||||
import android.content.Context
|
||||
import android.content.SharedPreferences
|
||||
import java.io.File
|
||||
|
||||
class DroidGuardPreferences(private val context: Context) {
|
||||
@Suppress("DEPRECATION")
|
||||
private val preferences by lazy { context.getSharedPreferences("droidguard", Context.MODE_PRIVATE) }
|
||||
private val systemDefaultPreferences by lazy {
|
||||
try {
|
||||
Context::class.java.getDeclaredMethod("getSharedPreferences", File::class.java, Int::class.javaPrimitiveType).invoke(context, File("/system/etc/microg.xml"), Context.MODE_PRIVATE) as SharedPreferences
|
||||
} catch (ignored: Exception) {
|
||||
null
|
||||
}
|
||||
}
|
||||
private var editing: Boolean = false
|
||||
private val updates: MutableMap<String, Any?> = hashMapOf()
|
||||
|
||||
var mode: Mode
|
||||
get() = try {
|
||||
getSettingsString(PREF_DROIDGUARD_MODE)?.let { Mode.valueOf(it) } ?: Mode.Connector
|
||||
} catch (e: Exception) {
|
||||
Mode.Connector
|
||||
}
|
||||
set(value) {
|
||||
if (editing) updates[PREF_DROIDGUARD_MODE] = value.name
|
||||
}
|
||||
|
||||
var networkServerUrl: String?
|
||||
get() = getSettingsString(PREF_DROIDGUARD_NETWORK_SERVER_URL)
|
||||
set(value) {
|
||||
if (editing) updates[PREF_DROIDGUARD_NETWORK_SERVER_URL] = value
|
||||
}
|
||||
|
||||
private fun getSettingsString(key: String): String? {
|
||||
return systemDefaultPreferences?.getString(key, null) ?: preferences.getString(key, null)
|
||||
}
|
||||
|
||||
fun edit(commands: DroidGuardPreferences.() -> Unit) {
|
||||
editing = true
|
||||
commands(this)
|
||||
preferences.edit().also {
|
||||
for ((k, v) in updates) {
|
||||
when (v) {
|
||||
is String -> it.putString(k, v)
|
||||
null -> it.remove(k)
|
||||
}
|
||||
}
|
||||
}.apply()
|
||||
editing = false
|
||||
}
|
||||
|
||||
enum class Mode {
|
||||
Disabled,
|
||||
Connector,
|
||||
Network
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val PREF_DROIDGUARD_MODE = "droidguard_mode"
|
||||
const val PREF_DROIDGUARD_NETWORK_SERVER_URL = "droidguard_network_server_url"
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,70 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2021, microG Project Team
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package org.microg.gms.droidguard
|
||||
|
||||
import android.content.Context
|
||||
import android.os.Bundle
|
||||
import android.util.Base64
|
||||
import com.android.volley.VolleyError
|
||||
import com.android.volley.toolbox.StringRequest
|
||||
import com.android.volley.toolbox.Volley
|
||||
import org.microg.gms.checkin.LastCheckinInfo
|
||||
import kotlin.coroutines.resume
|
||||
import kotlin.coroutines.resumeWithException
|
||||
import kotlin.coroutines.suspendCoroutine
|
||||
|
||||
interface DroidGuardResultCreator {
|
||||
suspend fun getResult(flow: String, data: Map<String, String>): ByteArray
|
||||
|
||||
companion object {
|
||||
fun getInstance(context: Context): DroidGuardResultCreator = when (DroidGuardPreferences(context).mode) {
|
||||
DroidGuardPreferences.Mode.Disabled -> throw RuntimeException("DroidGuard disabled")
|
||||
DroidGuardPreferences.Mode.Connector -> ConnectorDroidGuardResultCreator(context)
|
||||
DroidGuardPreferences.Mode.Network -> NetworkDroidGuardResultCreator(context)
|
||||
}
|
||||
|
||||
suspend fun getResult(context: Context, flow: String, data: Map<String, String>): ByteArray =
|
||||
getInstance(context).getResult(flow, data)
|
||||
}
|
||||
}
|
||||
|
||||
private class ConnectorDroidGuardResultCreator(private val context: Context) : DroidGuardResultCreator {
|
||||
override suspend fun getResult(flow: String, data: Map<String, String>): ByteArray = suspendCoroutine { continuation ->
|
||||
Thread {
|
||||
val bundle = Bundle()
|
||||
for (entry in data) {
|
||||
bundle.putString(entry.key, entry.value)
|
||||
}
|
||||
val conn = RemoteDroidGuardConnector(context)
|
||||
val dg = conn.guard(flow, LastCheckinInfo.read(context).androidId.toString(), bundle)
|
||||
if (dg == null) {
|
||||
continuation.resumeWithException(RuntimeException("No DroidGuard result"))
|
||||
} else if (dg.statusCode == 0 && dg.result != null) {
|
||||
continuation.resume(dg.result)
|
||||
} else {
|
||||
continuation.resumeWithException(RuntimeException("Status: " + dg.statusCode + ", error:" + dg.errorMsg))
|
||||
}
|
||||
}.start()
|
||||
}
|
||||
}
|
||||
|
||||
private class NetworkDroidGuardResultCreator(private val context: Context) : DroidGuardResultCreator {
|
||||
private val queue = Volley.newRequestQueue(context)
|
||||
private val url: String
|
||||
get() = DroidGuardPreferences(context).networkServerUrl ?: throw RuntimeException("Network URL required")
|
||||
|
||||
override suspend fun getResult(flow: String, data: Map<String, String>): ByteArray = suspendCoroutine { continuation ->
|
||||
queue.add(PostParamsStringRequest("$url?flow=$flow", data, {
|
||||
continuation.resume(Base64.decode(it, Base64.NO_WRAP + Base64.NO_PADDING + Base64.URL_SAFE))
|
||||
}, {
|
||||
continuation.resumeWithException(RuntimeException(it))
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
class PostParamsStringRequest(url: String, private val data: Map<String, String>, listener: (String) -> Unit, errorListener: (VolleyError) -> Unit) : StringRequest(Method.POST, url, listener, errorListener) {
|
||||
override fun getParams(): Map<String, String> = data
|
||||
}
|
@ -0,0 +1,93 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2020, microG Project Team
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package org.microg.gms.droidguard
|
||||
|
||||
import android.content.BroadcastReceiver
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.IntentFilter
|
||||
import android.util.Log
|
||||
import java.io.Serializable
|
||||
import kotlin.coroutines.resume
|
||||
import kotlin.coroutines.resumeWithException
|
||||
import kotlin.coroutines.suspendCoroutine
|
||||
|
||||
private const val ACTION_SERVICE_INFO_REQUEST = "org.microg.gms.droidguard.SERVICE_INFO_REQUEST"
|
||||
private const val ACTION_UPDATE_CONFIGURATION = "org.microg.gms.droidguard.UPDATE_CONFIGURATION"
|
||||
private const val ACTION_SERVICE_INFO_RESPONSE = "org.microg.gms.droidguard.SERVICE_INFO_RESPONSE"
|
||||
private const val EXTRA_SERVICE_INFO = "org.microg.gms.droidguard.SERVICE_INFO"
|
||||
private const val EXTRA_CONFIGURATION = "org.microg.gms.droidguard.CONFIGURATION"
|
||||
private const val TAG = "GmsGcmStatusInfo"
|
||||
|
||||
data class ServiceInfo(val configuration: ServiceConfiguration) : Serializable
|
||||
|
||||
data class ServiceConfiguration(val mode: DroidGuardPreferences.Mode, val networkServerUrl: String?) : Serializable {
|
||||
fun saveToPrefs(context: Context) {
|
||||
DroidGuardPreferences(context).edit {
|
||||
mode = this@ServiceConfiguration.mode
|
||||
networkServerUrl = this@ServiceConfiguration.networkServerUrl
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun DroidGuardPreferences.toConfiguration(): ServiceConfiguration = ServiceConfiguration(mode, networkServerUrl)
|
||||
|
||||
class ServiceInfoReceiver : BroadcastReceiver() {
|
||||
private fun sendInfoResponse(context: Context) {
|
||||
context.sendOrderedBroadcast(Intent(ACTION_SERVICE_INFO_RESPONSE).apply {
|
||||
setPackage(context.packageName)
|
||||
putExtra(EXTRA_SERVICE_INFO, ServiceInfo(DroidGuardPreferences(context).toConfiguration()))
|
||||
}, null)
|
||||
}
|
||||
|
||||
override fun onReceive(context: Context, intent: Intent) {
|
||||
try {
|
||||
when (intent.action) {
|
||||
ACTION_UPDATE_CONFIGURATION -> {
|
||||
(intent.getSerializableExtra(EXTRA_CONFIGURATION) as? ServiceConfiguration)?.saveToPrefs(context)
|
||||
}
|
||||
}
|
||||
sendInfoResponse(context)
|
||||
} catch (e: Exception) {
|
||||
Log.w(TAG, e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun sendToServiceInfoReceiver(intent: Intent, context: Context): ServiceInfo = suspendCoroutine {
|
||||
context.registerReceiver(object : BroadcastReceiver() {
|
||||
override fun onReceive(context: Context, intent: Intent) {
|
||||
context.unregisterReceiver(this)
|
||||
val serviceInfo = try {
|
||||
intent.getSerializableExtra(EXTRA_SERVICE_INFO) as ServiceInfo
|
||||
} catch (e: Exception) {
|
||||
it.resumeWithException(e)
|
||||
return
|
||||
}
|
||||
try {
|
||||
it.resume(serviceInfo)
|
||||
} catch (e: Exception) {
|
||||
Log.w(TAG, e)
|
||||
}
|
||||
}
|
||||
}, IntentFilter(ACTION_SERVICE_INFO_RESPONSE))
|
||||
try {
|
||||
context.sendOrderedBroadcast(intent, null)
|
||||
} catch (e: Exception) {
|
||||
it.resumeWithException(e)
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun getDroidGuardServiceInfo(context: Context): ServiceInfo = sendToServiceInfoReceiver(
|
||||
Intent(context, ServiceInfoReceiver::class.java).apply {
|
||||
action = ACTION_SERVICE_INFO_REQUEST
|
||||
}, context)
|
||||
|
||||
suspend fun setDroidGuardServiceConfiguration(context: Context, configuration: ServiceConfiguration): ServiceInfo = sendToServiceInfoReceiver(
|
||||
Intent(context, ServiceInfoReceiver::class.java).apply {
|
||||
action = ACTION_UPDATE_CONFIGURATION
|
||||
putExtra(EXTRA_CONFIGURATION, configuration)
|
||||
}, context)
|
@ -0,0 +1,204 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2021, microG Project Team
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package org.microg.gms.recaptcha
|
||||
|
||||
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.R
|
||||
import com.google.android.gms.safetynet.SafetyNetStatusCodes.*
|
||||
import org.microg.gms.droidguard.DroidGuardResultCreator
|
||||
import java.io.ByteArrayInputStream
|
||||
import java.net.URLEncoder
|
||||
import java.security.MessageDigest
|
||||
import kotlin.math.min
|
||||
|
||||
private const val TAG = "GmsReCAPTCHA"
|
||||
|
||||
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()
|
||||
}
|
||||
|
||||
@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
|
||||
}
|
||||
addJavascriptInterface(object {
|
||||
@JavascriptInterface
|
||||
fun challengeReady() {
|
||||
Log.d(TAG, "challengeReady()")
|
||||
runOnUiThread { webView?.loadUrl("javascript: RecaptchaMFrame.show(${min(widthPixels / density, 400f)}, ${min(heightPixels / 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 -> receiver?.send(ERROR, Bundle().apply { putString("error", "Invalid Input Argument"); putInt("errorCode", ERROR) })
|
||||
2 -> receiver?.send(TIMEOUT, Bundle().apply { putString("error", "Session Timeout"); putInt("errorCode", TIMEOUT) })
|
||||
7 -> receiver?.send(RECAPTCHA_INVALID_SITEKEY, Bundle().apply { putString("error", "Invalid Site Key"); putInt("errorCode", RECAPTCHA_INVALID_SITEKEY) })
|
||||
8 -> receiver?.send(RECAPTCHA_INVALID_KEYTYPE, Bundle().apply { putString("error", "Invalid Type of Site Key"); putInt("errorCode", RECAPTCHA_INVALID_KEYTYPE) })
|
||||
9 -> receiver?.send(RECAPTCHA_INVALID_PACKAGE_NAME, Bundle().apply { putString("error", "Invalid Package Name for App"); putInt("errorCode", RECAPTCHA_INVALID_PACKAGE_NAME) })
|
||||
else -> receiver?.send(ERROR, Bundle().apply { putString("error", "error"); putInt("errorCode", ERROR) })
|
||||
}
|
||||
if (finish) this@ReCaptchaActivity.finish()
|
||||
}
|
||||
|
||||
@JavascriptInterface
|
||||
fun onResize(width: Int, height: Int) {
|
||||
Log.d(TAG, "onResize($width, $height)")
|
||||
if (webView?.visibility == View.VISIBLE) {
|
||||
runOnUiThread { setWebViewSize(width, height, true) }
|
||||
} else {
|
||||
runOnUiThread { 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) {
|
||||
runOnUiThread { webView?.loadUrl("javascript: RecaptchaMFrame.shown($width, $height, $visible);") }
|
||||
} else {
|
||||
runOnUiThread {
|
||||
setWebViewSize(width, height, visible)
|
||||
loading?.visibility = if (visible) View.GONE else View.VISIBLE
|
||||
webView?.visibility = if (visible) View.VISIBLE else View.GONE
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@JavascriptInterface
|
||||
fun requestToken(s: String, b: Boolean) {
|
||||
Log.d(TAG, "requestToken($s, $b)")
|
||||
runOnUiThread {
|
||||
val cert = webView?.certificate?.let { Base64.encodeToString(SslCertificate.saveState(it).getByteArray("x509-certificate"), Base64.URL_SAFE + Base64.NO_PADDING + Base64.NO_WRAP) }
|
||||
?: ""
|
||||
val params = StringBuilder(params).appendUrlEncodedParam("c", s).appendUrlEncodedParam("sc", cert).appendUrlEncodedParam("mt", System.currentTimeMillis().toString()).toString()
|
||||
val flow = "recaptcha-android-${if (b) "verify" else "reload"}"
|
||||
lifecycleScope.launchWhenResumed {
|
||||
updateToken(flow, params)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@JavascriptInterface
|
||||
fun verifyCallback(token: String) {
|
||||
Log.d(TAG, "verifyCallback($token)")
|
||||
receiver?.send(0, Bundle().apply { putString("token", token) })
|
||||
finish()
|
||||
}
|
||||
}, "RecaptchaEmbedder")
|
||||
}
|
||||
lifecycleScope.launchWhenResumed {
|
||||
open()
|
||||
}
|
||||
}
|
||||
|
||||
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 = Base64.encodeToString(DroidGuardResultCreator.getResult(this, flow, map), 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 = Base64.encodeToString(DroidGuardResultCreator.getResult(this, "recaptcha-android-frame", map), 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"
|
||||
}
|
||||
}
|
@ -0,0 +1,156 @@
|
||||
/*
|
||||
* 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.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.CommonStatusCodes
|
||||
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.safetynet.AttestationData
|
||||
import com.google.android.gms.safetynet.RecaptchaResultData
|
||||
import com.google.android.gms.safetynet.internal.ISafetyNetCallbacks
|
||||
import com.google.android.gms.safetynet.internal.ISafetyNetService
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.microg.gms.BaseService
|
||||
import org.microg.gms.checkin.LastCheckinInfo
|
||||
import org.microg.gms.common.GmsService
|
||||
import org.microg.gms.common.PackageUtils
|
||||
import org.microg.gms.droidguard.DroidGuardResultCreator
|
||||
import org.microg.gms.recaptcha.ReCaptchaActivity
|
||||
import org.microg.gms.recaptcha.appendUrlEncodedParam
|
||||
import java.io.IOException
|
||||
import java.util.*
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class SafetyNetClientServiceImpl(private val context: Context, private val packageName: String, private val lifecycle: Lifecycle) : ISafetyNetService.Stub(), LifecycleOwner {
|
||||
override fun getLifecycle(): Lifecycle = lifecycle
|
||||
|
||||
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.onAttestationData(Status(CommonStatusCodes.DEVELOPER_ERROR), null)
|
||||
return
|
||||
}
|
||||
if (!SafetyNetPrefs.get(context).isEnabled) {
|
||||
Log.d(TAG, "ignoring SafetyNet request, it's disabled")
|
||||
callbacks.onAttestationData(Status.CANCELED, null)
|
||||
return
|
||||
}
|
||||
|
||||
lifecycleScope.launchWhenStarted {
|
||||
try {
|
||||
val attestation = Attestation(context, packageName)
|
||||
attestation.buildPayload(nonce)
|
||||
try {
|
||||
val dg = DroidGuardResultCreator.getResult(context, "attest", mapOf("contentBinding" to attestation.payloadHashBase64))
|
||||
attestation.setDroidGaurdResult(Base64.encodeToString(dg, Base64.NO_WRAP + Base64.NO_PADDING + Base64.URL_SAFE))
|
||||
} catch (e: Exception) {
|
||||
if (SafetyNetPrefs.get(context).isOfficial) throw e
|
||||
Log.w(TAG, e)
|
||||
null
|
||||
}
|
||||
val data = withContext(Dispatchers.IO) { AttestationData(attestation.attest(apiKey)) }
|
||||
callbacks.onAttestationData(Status.SUCCESS, data)
|
||||
} catch (e: IOException) {
|
||||
Log.w(TAG, e)
|
||||
callbacks.onAttestationData(Status.INTERNAL_ERROR, null)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun getSharedUuid(callbacks: ISafetyNetCallbacks) {
|
||||
PackageUtils.checkPackageUid(context, packageName, getCallingUid())
|
||||
PackageUtils.assertExtendedAccess(context)
|
||||
|
||||
// TODO
|
||||
Log.d(TAG, "dummy Method: getSharedUuid")
|
||||
callbacks.onString("aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa")
|
||||
}
|
||||
|
||||
override fun lookupUri(callbacks: ISafetyNetCallbacks, s1: String, threatTypes: IntArray, i: Int, s2: String) {
|
||||
Log.d(TAG, "unimplemented Method: lookupUri")
|
||||
}
|
||||
|
||||
override fun init(callbacks: ISafetyNetCallbacks) {
|
||||
Log.d(TAG, "dummy Method: init")
|
||||
callbacks.onBoolean(Status.SUCCESS, true)
|
||||
}
|
||||
|
||||
override fun getHarmfulAppsList(callbacks: ISafetyNetCallbacks) {
|
||||
Log.d(TAG, "dummy Method: unknown4")
|
||||
callbacks.onHarmfulAppsData(Status.SUCCESS, ArrayList())
|
||||
}
|
||||
|
||||
override fun verifyWithRecaptcha(callbacks: ISafetyNetCallbacks, siteKey: String?) {
|
||||
if (siteKey == null) {
|
||||
callbacks.onAttestationData(Status(CommonStatusCodes.DEVELOPER_ERROR), null)
|
||||
return
|
||||
}
|
||||
if (!SafetyNetPrefs.get(context).isEnabled) {
|
||||
Log.d(TAG, "ignoring SafetyNet request, it's disabled")
|
||||
callbacks.onAttestationData(Status.CANCELED, null)
|
||||
return
|
||||
}
|
||||
val intent = Intent(context, ReCaptchaActivity::class.java)
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY)
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS)
|
||||
val params = StringBuilder()
|
||||
params.appendUrlEncodedParam("k", siteKey)
|
||||
.appendUrlEncodedParam("di", LastCheckinInfo.read(context).androidId.toString())
|
||||
.appendUrlEncodedParam("pk", packageName)
|
||||
.appendUrlEncodedParam("sv", SDK_INT.toString())
|
||||
.appendUrlEncodedParam("gv", "20.47.14 (040306-{{cl}})")
|
||||
.appendUrlEncodedParam("gm", "260")
|
||||
.appendUrlEncodedParam("as", Base64.encodeToString(Attestation.getPackageFileDigest(context, packageName), Base64.URL_SAFE or Base64.NO_WRAP or Base64.NO_PADDING))
|
||||
for (signature in Attestation.getPackageSignatures(context, packageName)) {
|
||||
params.appendUrlEncodedParam("ac", Base64.encodeToString(signature, Base64.URL_SAFE or Base64.NO_WRAP or Base64.NO_PADDING))
|
||||
}
|
||||
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) {
|
||||
callbacks.onRecaptchaResult(Status(resultData.getInt("errorCode"), resultData.getString("error")), null)
|
||||
} else {
|
||||
callbacks.onRecaptchaResult(Status.SUCCESS, RecaptchaResultData().apply { token = resultData.getString("token") })
|
||||
}
|
||||
}
|
||||
})
|
||||
context.startActivity(intent)
|
||||
}
|
||||
|
||||
override fun onTransact(code: Int, data: Parcel, reply: Parcel?, flags: Int): Boolean {
|
||||
if (super.onTransact(code, data, reply, flags)) return true
|
||||
Log.d(TAG, "onTransact [unknown]: $code, $data, $flags")
|
||||
return false
|
||||
}
|
||||
}
|
@ -1,9 +1,9 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2020, microG Project Team
|
||||
* SPDX-FileCopyrightText: 2021, microG Project Team
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package org.microg.gms.snet
|
||||
package org.microg.gms.safetynet
|
||||
|
||||
import android.content.BroadcastReceiver
|
||||
import android.content.Context
|
@ -13,9 +13,9 @@ import androidx.navigation.fragment.findNavController
|
||||
import com.google.android.gms.R
|
||||
import com.google.android.gms.databinding.SafetyNetFragmentBinding
|
||||
import org.microg.gms.checkin.getCheckinServiceInfo
|
||||
import org.microg.gms.snet.ServiceInfo
|
||||
import org.microg.gms.snet.getSafetyNetServiceInfo
|
||||
import org.microg.gms.snet.setSafetyNetServiceConfiguration
|
||||
import org.microg.gms.safetynet.ServiceInfo
|
||||
import org.microg.gms.safetynet.getSafetyNetServiceInfo
|
||||
import org.microg.gms.safetynet.setSafetyNetServiceConfiguration
|
||||
|
||||
class SafetyNetFragment : Fragment(R.layout.safety_net_fragment) {
|
||||
|
||||
|
64
play-services-core/src/main/res/drawable/ic_recaptcha.xml
Normal file
64
play-services-core/src/main/res/drawable/ic_recaptcha.xml
Normal file
@ -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>
|
@ -25,7 +25,9 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="?android:attr/colorBackground"
|
||||
android:orientation="vertical">
|
||||
android:divider="?attr/dividerHorizontal"
|
||||
android:orientation="vertical"
|
||||
android:showDividers="middle">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
@ -58,15 +60,12 @@
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<View
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0.25dp"
|
||||
android:background="?android:attr/textColorSecondary" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
android:divider="?attr/dividerHorizontal"
|
||||
android:orientation="vertical"
|
||||
android:showDividers="middle">
|
||||
|
||||
<Button
|
||||
android:id="@+id/permission_allow_button"
|
||||
@ -78,11 +77,6 @@
|
||||
android:text="@string/allow">
|
||||
</Button>
|
||||
|
||||
<View
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0.25dp"
|
||||
android:background="?android:attr/textColorSecondary" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/permission_deny_button"
|
||||
android:layout_width="match_parent"
|
||||
|
68
play-services-core/src/main/res/layout/recaptcha_window.xml
Normal file
68
play-services-core/src/main/res/layout/recaptcha_window.xml
Normal file
@ -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>
|
Loading…
Reference in New Issue
Block a user