From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 From: Danny Lin Date: Mon, 11 Oct 2021 19:59:51 -0700 Subject: [PATCH 1/8] Alter model name to avoid SafetyNet HW attestation enforcement As of September 2, Google is enforcing SafetyNet's previously opportunistic hardware-backed attestation based on device information. Append a space to the device model name in order to avoid such enforcement. Also contains: Spoof build fingerprint for Google Play Services SafetyNet's CTS profile attestation checks whether Build.FINGERPRINT matches that of the device's stock OS, which has passed CTS testing. Spoof the fingerprint for Google Play Services to help pass SafetyNet. We used to set the real system build fingerprint to the stock one, but Android relies on each build having a unique fingerprint in order to clear the correct caches and update persistent state for system changes. On devices that no longer receive updates from the OEM, the build fingerprint never changes and Android doesn't account for updates correctly, which causes issues when updating without wiping data. Only spoofing the fingerprint for Google Play Services fixes this issue. Corresponding vendor commit: "Only use stock build fingerprint for Google Play Services" NB: This code is under the gmscompat package, but it does not depend on any code from gmscompat. Change-Id: I26a2498eb2e2163933303b03f6d516e5fb30fe51 * We don't need to spoof the fingerprint here since we do it globally, but we use the Build field spoofing code it added for model Change-Id: Ib7779e0aae40cab3730a56785e9231896917ab0a --- core/java/android/app/Instrumentation.java | 4 ++ .../internal/gmscompat/AttestationHooks.java | 59 +++++++++++++++++++ 2 files changed, 63 insertions(+) create mode 100644 core/java/com/android/internal/gmscompat/AttestationHooks.java diff --git a/core/java/android/app/Instrumentation.java b/core/java/android/app/Instrumentation.java index 9720e9f47f83..e19f3fc4db85 100644 --- a/core/java/android/app/Instrumentation.java +++ b/core/java/android/app/Instrumentation.java @@ -57,6 +57,8 @@ import android.view.WindowManagerGlobal; import com.android.internal.content.ReferrerIntent; +import com.android.internal.gmscompat.AttestationHooks; + import java.io.File; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -1154,6 +1156,7 @@ public class Instrumentation { Application app = getFactory(context.getPackageName()) .instantiateApplication(cl, className); app.attach(context); + AttestationHooks.initApplicationBeforeOnCreate(app); return app; } @@ -1171,6 +1174,7 @@ public class Instrumentation { ClassNotFoundException { Application app = (Application)clazz.newInstance(); app.attach(context); + AttestationHooks.initApplicationBeforeOnCreate(app); return app; } diff --git a/core/java/com/android/internal/gmscompat/AttestationHooks.java b/core/java/com/android/internal/gmscompat/AttestationHooks.java new file mode 100644 index 000000000000..621156eb84b9 --- /dev/null +++ b/core/java/com/android/internal/gmscompat/AttestationHooks.java @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * 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.android.internal.gmscompat; + +import android.app.Application; +import android.os.Build; +import android.os.SystemProperties; +import android.util.Log; + +import java.lang.reflect.Field; + +/** @hide */ +public final class AttestationHooks { + private static final String TAG = "GmsCompat/Attestation"; + private static final String PACKAGE_GMS = "com.google.android.gms"; + + private AttestationHooks() { } + + private static void setBuildField(String key, String value) { + try { + // Unlock + Field field = Build.class.getDeclaredField(key); + field.setAccessible(true); + + // Edit + field.set(null, value); + + // Lock + field.setAccessible(false); + } catch (NoSuchFieldException | IllegalAccessException e) { + Log.e(TAG, "Failed to spoof Build." + key, e); + } + } + + private static void spoofBuildGms() { + // Alter model name to avoid hardware attestation enforcement + setBuildField("MODEL", Build.MODEL + " "); + } + + public static void initApplicationBeforeOnCreate(Application app) { + if (PACKAGE_GMS.equals(app.getPackageName())) { + spoofBuildGms(); + } + } +} From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 From: Danny Lin Date: Mon, 11 Oct 2021 20:00:44 -0700 Subject: [PATCH 2/8] keystore: Block key attestation for SafetyNet SafetyNet (part of Google Play Services) opportunistically uses hardware-backed key attestation via KeyStore as a strong integrity check. This causes SafetyNet to fail on custom ROMs because the verified boot key and bootloader unlock state can be detected from attestation certificates. As a workaround, we can take advantage of the fact that SafetyNet's usage of key attestation is opportunistic (i.e. falls back to basic integrity checks if it fails) and prevent it from getting the attestation certificate chain from KeyStore. This is done by checking the stack for DroidGuard, which is the codename for SafetyNet, and pretending that the device doesn't support key attestation. Key attestation has only been blocked for SafetyNet specifically, as Google Play Services and other apps have many valid reasons to use it. For example, it appears to be involved in Google's mobile security key ferature. Change-Id: I5146439d47f42dc6231cb45c4dab9f61540056f6 --- .../internal/gmscompat/AttestationHooks.java | 16 ++++++++++++++++ .../security/keystore/AndroidKeyStoreSpi.java | 4 ++++ 2 files changed, 20 insertions(+) diff --git a/core/java/com/android/internal/gmscompat/AttestationHooks.java b/core/java/com/android/internal/gmscompat/AttestationHooks.java index 621156eb84b9..fe12dfe02a9f 100644 --- a/core/java/com/android/internal/gmscompat/AttestationHooks.java +++ b/core/java/com/android/internal/gmscompat/AttestationHooks.java @@ -22,12 +22,15 @@ import android.os.SystemProperties; import android.util.Log; import java.lang.reflect.Field; +import java.util.Arrays; /** @hide */ public final class AttestationHooks { private static final String TAG = "GmsCompat/Attestation"; private static final String PACKAGE_GMS = "com.google.android.gms"; + private static volatile boolean sIsGms = false; + private AttestationHooks() { } private static void setBuildField(String key, String value) { @@ -53,7 +56,20 @@ public final class AttestationHooks { public static void initApplicationBeforeOnCreate(Application app) { if (PACKAGE_GMS.equals(app.getPackageName())) { + sIsGms = true; spoofBuildGms(); } } + + private static boolean isCallerSafetyNet() { + return Arrays.stream(Thread.currentThread().getStackTrace()) + .anyMatch(elem -> elem.getClassName().contains("DroidGuard")); + } + + public static void onEngineGetCertificateChain() { + // Check stack for SafetyNet + if (sIsGms && isCallerSafetyNet()) { + throw new UnsupportedOperationException(); + } + } } diff --git a/keystore/java/android/security/keystore/AndroidKeyStoreSpi.java b/keystore/java/android/security/keystore/AndroidKeyStoreSpi.java index 51c42520ccc9..b77ddc711492 100644 --- a/keystore/java/android/security/keystore/AndroidKeyStoreSpi.java +++ b/keystore/java/android/security/keystore/AndroidKeyStoreSpi.java @@ -30,6 +30,8 @@ import android.security.keystore.SecureKeyImportUnavailableException; import android.security.keystore.WrappedKeyEntry; import android.util.Log; +import com.android.internal.gmscompat.AttestationHooks; + import libcore.util.EmptyArray; import java.io.ByteArrayInputStream; @@ -113,6 +115,8 @@ public class AndroidKeyStoreSpi extends KeyStoreSpi { @Override public Certificate[] engineGetCertificateChain(String alias) { + AttestationHooks.onEngineGetCertificateChain(); + if (alias == null) { throw new NullPointerException("alias == null"); } From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 From: Anirudh Gupta Date: Wed, 4 Jan 2023 18:20:56 +0000 Subject: [PATCH 3/8] AttestationHooks: Set shipping level to 32 for devices >=33 If ro.product.first_api_level is 33, it's forced to use HW attestation. Setting it to 32 allows for software attestation and passing CTS. Change-Id: Ie47fd00b009c93580ec8c950d223c60ed63a0d2f --- .../internal/gmscompat/AttestationHooks.java | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/core/java/com/android/internal/gmscompat/AttestationHooks.java b/core/java/com/android/internal/gmscompat/AttestationHooks.java index fe12dfe02a9f..f512adc3985b 100644 --- a/core/java/com/android/internal/gmscompat/AttestationHooks.java +++ b/core/java/com/android/internal/gmscompat/AttestationHooks.java @@ -49,9 +49,28 @@ public final class AttestationHooks { } } + private static void setVersionField(String key, Integer value) { + try { + // Unlock + Field field = Build.VERSION.class.getDeclaredField(key); + field.setAccessible(true); + + // Edit + field.set(null, value); + + // Lock + field.setAccessible(false); + } catch (NoSuchFieldException | IllegalAccessException e) { + Log.e(TAG, "Failed to spoof Build.VERSION." + key, e); + } + } + private static void spoofBuildGms() { // Alter model name to avoid hardware attestation enforcement setBuildField("MODEL", Build.MODEL + " "); + if (Build.VERSION.DEVICE_INITIAL_SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + setVersionField("DEVICE_INITIAL_SDK_INT", Build.VERSION_CODES.S_V2); + } } public static void initApplicationBeforeOnCreate(Application app) { From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 From: Danny Lin Date: Mon, 1 Nov 2021 20:06:48 -0700 Subject: [PATCH 4/8] Limit SafetyNet workarounds to unstable GMS process The unstable process is where SafetyNet attestation actually runs, so we only need to spoof the model in that process. Leaving other processes fixes various issues caused by model detection and flag provisioning, including screen-off Voice Match in Google Assistant, broken At a Glance weather and settings on Android 12, and more. Change-Id: Idcf663907a6c3d0408dbd45b1ac53c9eb4200df8 --- .../java/com/android/internal/gmscompat/AttestationHooks.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/core/java/com/android/internal/gmscompat/AttestationHooks.java b/core/java/com/android/internal/gmscompat/AttestationHooks.java index f512adc3985b..c1021dd2eb22 100644 --- a/core/java/com/android/internal/gmscompat/AttestationHooks.java +++ b/core/java/com/android/internal/gmscompat/AttestationHooks.java @@ -28,6 +28,7 @@ import java.util.Arrays; public final class AttestationHooks { private static final String TAG = "GmsCompat/Attestation"; private static final String PACKAGE_GMS = "com.google.android.gms"; + private static final String PROCESS_UNSTABLE = "com.google.android.gms.unstable"; private static volatile boolean sIsGms = false; @@ -74,7 +75,8 @@ public final class AttestationHooks { } public static void initApplicationBeforeOnCreate(Application app) { - if (PACKAGE_GMS.equals(app.getPackageName())) { + if (PACKAGE_GMS.equals(app.getPackageName()) && + PROCESS_UNSTABLE.equals(Application.getProcessName())) { sIsGms = true; spoofBuildGms(); } From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 From: Dyneteve Date: Tue, 23 Aug 2022 18:57:05 +0200 Subject: [PATCH 5/8] gmscompat: Apply the SafetyNet workaround to Play Store aswell Play Store is used for the new Play Integrity API, extend the hack to it aswell Test: Device Integrity and Basic Integrity passes. Change-Id: Id607cdff0b902f285a6c1b769c0a4ee4202842b1 --- .../android/internal/gmscompat/AttestationHooks.java | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/core/java/com/android/internal/gmscompat/AttestationHooks.java b/core/java/com/android/internal/gmscompat/AttestationHooks.java index c1021dd2eb22..6a4aab000fe0 100644 --- a/core/java/com/android/internal/gmscompat/AttestationHooks.java +++ b/core/java/com/android/internal/gmscompat/AttestationHooks.java @@ -28,9 +28,11 @@ import java.util.Arrays; public final class AttestationHooks { private static final String TAG = "GmsCompat/Attestation"; private static final String PACKAGE_GMS = "com.google.android.gms"; + private static final String PACKAGE_FINSKY = "com.android.vending"; private static final String PROCESS_UNSTABLE = "com.google.android.gms.unstable"; private static volatile boolean sIsGms = false; + private static volatile boolean sIsFinsky = false; private AttestationHooks() { } @@ -80,6 +82,11 @@ public final class AttestationHooks { sIsGms = true; spoofBuildGms(); } + + if (PACKAGE_FINSKY.equals(app.getPackageName())) { + sIsFinsky = true; + spoofBuildGms(); + } } private static boolean isCallerSafetyNet() { @@ -92,5 +99,10 @@ public final class AttestationHooks { if (sIsGms && isCallerSafetyNet()) { throw new UnsupportedOperationException(); } + + // Check stack for PlayIntegrity + if (sIsFinsky) { + throw new UnsupportedOperationException(); + } } } From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 From: Dyneteve Date: Thu, 8 Sep 2022 14:39:52 +0200 Subject: [PATCH 6/8] gmscompat: Use Nexus 6P fingerprint for CTS/Integrity Google seems to have patched the KM block to Play Store in record time, but is still not enforced for anything under android N. Since we moved to angler FP we don't need to spoof model to Play Store anymore, however the KM block is still needed. Test: Run Play Intregrity Attestation Change-Id: Ic2401a6e40ddfc4318a1d0faa87e42eb118ac3d1 --- .../java/com/android/internal/gmscompat/AttestationHooks.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/java/com/android/internal/gmscompat/AttestationHooks.java b/core/java/com/android/internal/gmscompat/AttestationHooks.java index 6a4aab000fe0..6bd12a1c1e03 100644 --- a/core/java/com/android/internal/gmscompat/AttestationHooks.java +++ b/core/java/com/android/internal/gmscompat/AttestationHooks.java @@ -69,7 +69,8 @@ public final class AttestationHooks { } private static void spoofBuildGms() { - // Alter model name to avoid hardware attestation enforcement + // Alter model name and fingerprint to avoid hardware attestation enforcement + setBuildField("FINGERPRINT", "google/angler/angler:6.0/MDB08L/2343525:user/release-keys"); setBuildField("MODEL", Build.MODEL + " "); if (Build.VERSION.DEVICE_INITIAL_SDK_INT >= Build.VERSION_CODES.TIRAMISU) { setVersionField("DEVICE_INITIAL_SDK_INT", Build.VERSION_CODES.S_V2); @@ -85,7 +86,6 @@ public final class AttestationHooks { if (PACKAGE_FINSKY.equals(app.getPackageName())) { sIsFinsky = true; - spoofBuildGms(); } } From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 From: Dyneteve Date: Wed, 8 Feb 2023 15:21:01 +0000 Subject: [PATCH 7/8] gmscompat: Make CTS/Play Integrity pass again The logic behind CTS and Play Integrity has been updated today it now checks the product and model names against the fingerprint and if they do not match the CTS profile will fail. Also while we are at it use a newer FP from Pixel XL and add logging for key attestation blocking for debugging. Test: Boot, check for CTS and Play Integrity Change-Id: I089d5ef935bba40338e10c795ea7d181103ffd15 --- .../internal/gmscompat/AttestationHooks.java | 22 ++++++++----------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/core/java/com/android/internal/gmscompat/AttestationHooks.java b/core/java/com/android/internal/gmscompat/AttestationHooks.java index 6bd12a1c1e03..b10cb04cb4f3 100644 --- a/core/java/com/android/internal/gmscompat/AttestationHooks.java +++ b/core/java/com/android/internal/gmscompat/AttestationHooks.java @@ -70,11 +70,11 @@ public final class AttestationHooks { private static void spoofBuildGms() { // Alter model name and fingerprint to avoid hardware attestation enforcement - setBuildField("FINGERPRINT", "google/angler/angler:6.0/MDB08L/2343525:user/release-keys"); - setBuildField("MODEL", Build.MODEL + " "); - if (Build.VERSION.DEVICE_INITIAL_SDK_INT >= Build.VERSION_CODES.TIRAMISU) { - setVersionField("DEVICE_INITIAL_SDK_INT", Build.VERSION_CODES.S_V2); - } + setBuildField("FINGERPRINT", "google/marlin/marlin:7.1.2/NJH47F/4146041:user/release-keys"); + setBuildField("PRODUCT", "marlin"); + setBuildField("DEVICE", "marlin"); + setBuildField("MODEL", "Pixel XL"); + setVersionField("DEVICE_INITIAL_SDK_INT", Build.VERSION_CODES.N_MR1); } public static void initApplicationBeforeOnCreate(Application app) { @@ -90,18 +90,14 @@ public final class AttestationHooks { } private static boolean isCallerSafetyNet() { - return Arrays.stream(Thread.currentThread().getStackTrace()) + return sIsGms && Arrays.stream(Thread.currentThread().getStackTrace()) .anyMatch(elem -> elem.getClassName().contains("DroidGuard")); } public static void onEngineGetCertificateChain() { - // Check stack for SafetyNet - if (sIsGms && isCallerSafetyNet()) { - throw new UnsupportedOperationException(); - } - - // Check stack for PlayIntegrity - if (sIsFinsky) { + // Check stack for SafetyNet or Play Integrity + if (isCallerSafetyNet() || sIsFinsky) { + Log.i(TAG, "Blocked key attestation sIsGms=" + sIsGms + " sIsFinsky=" + sIsFinsky); throw new UnsupportedOperationException(); } } From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 From: Davide Garberi Date: Wed, 8 Nov 2023 21:36:02 +0100 Subject: [PATCH 8/8] gmscompat: Use new info Change-Id: I3cb0c55d28249b73ecc53be83bed030304c782d9 --- .../android/internal/gmscompat/AttestationHooks.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/core/java/com/android/internal/gmscompat/AttestationHooks.java b/core/java/com/android/internal/gmscompat/AttestationHooks.java index b10cb04cb4f3..04a536d8073d 100644 --- a/core/java/com/android/internal/gmscompat/AttestationHooks.java +++ b/core/java/com/android/internal/gmscompat/AttestationHooks.java @@ -70,11 +70,11 @@ public final class AttestationHooks { private static void spoofBuildGms() { // Alter model name and fingerprint to avoid hardware attestation enforcement - setBuildField("FINGERPRINT", "google/marlin/marlin:7.1.2/NJH47F/4146041:user/release-keys"); - setBuildField("PRODUCT", "marlin"); - setBuildField("DEVICE", "marlin"); - setBuildField("MODEL", "Pixel XL"); - setVersionField("DEVICE_INITIAL_SDK_INT", Build.VERSION_CODES.N_MR1); + setBuildField("DEVICE", "bullhead"); + setBuildField("FINGERPRINT", "google/bullhead/bullhead:8.0.0/OPR6.170623.013/4283548:user/release-keys"); + setBuildField("MODEL", "Nexus 5X"); + setBuildField("PRODUCT", "bullhead"); + setVersionField("DEVICE_INITIAL_SDK_INT", Build.VERSION_CODES.N); } public static void initApplicationBeforeOnCreate(Application app) {