Verified Commit 1a809e0e authored by Marvin W.'s avatar Marvin W. 🐿
Browse files

SafetyNet updates

- Add more API details
- preliminary support for SafetyNet reCAPTCHA
- prepare for improved DroidGuard handling
parent 7f131c0c
......@@ -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'
......
package com.google.android.gms.safetynet;
parcelable HarmfulAppsInfo;
package com.google.android.gms.safetynet;
parcelable RecaptchaResultData;
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;
}
\ No newline at end of file
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;
}
\ No newline at end of file
void verifyWithRecaptcha(ISafetyNetCallbacks callbacks, String siteKey) = 5;
}
/*
* 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);
}
......@@ -14,24 +14,19 @@
* limitations under the License.
*/
package org.microg.gms.snet;
package com.google.android.gms.safetynet;
import android.os.RemoteException;
import org.microg.safeparcel.AutoSafeParcelable;
import com.google.android.gms.common.internal.GetServiceRequest;
import com.google.android.gms.common.internal.IGmsCallbacks;
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;
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);
}
public static final Creator<HarmfulAppsInfo> CREATOR = new AutoCreator<HarmfulAppsInfo>(HarmfulAppsInfo.class);
}
/*
* 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);
}
/*
* 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);
}
/*
* 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;
}
/*
* 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" />
......
/*
* 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) {