From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Danny Lin <danny@kdrag0n.dev>
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 d4385549da02..61e4176392f7 100644
--- a/core/java/android/app/Instrumentation.java
+++ b/core/java/android/app/Instrumentation.java
@@ -53,6 +53,8 @@ import android.view.Window;
 
 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;
@@ -1119,6 +1121,7 @@ public class Instrumentation {
         Application app = getFactory(context.getPackageName())
                 .instantiateApplication(cl, className);
         app.attach(context);
+        AttestationHooks.initApplicationBeforeOnCreate(app);
         return app;
     }
     
@@ -1136,6 +1139,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 <danny@kdrag0n.dev>
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 4c007cb70ba2..7dcd79197aa5 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 java.io.ByteArrayInputStream;
 import java.io.IOException;
 import java.io.InputStream;
@@ -104,6 +106,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 <anirudhgupta109@aosip.dev>
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 <danny@kdrag0n.dev>
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 <dyneteve@hentaios.com>
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 <dyneteve@hentaios.com>
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 <dyneteve@hentaios.com>
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 <dade.garberi@gmail.com>
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) {
