• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2020 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package android.server.biometrics;
18 
19 import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
20 
21 import android.content.ComponentName;
22 import android.hardware.biometrics.BiometricManager;
23 import android.hardware.biometrics.BiometricPrompt;
24 import android.hardware.biometrics.SensorProperties;
25 import android.os.ParcelFileDescriptor;
26 import android.os.Process;
27 import android.os.SystemProperties;
28 import android.security.keystore.KeyGenParameterSpec;
29 import android.security.keystore.KeyProperties;
30 import android.server.wm.Condition;
31 import android.util.Log;
32 
33 import androidx.annotation.NonNull;
34 import androidx.annotation.Nullable;
35 
36 import com.android.server.biometrics.nano.BiometricServiceStateProto;
37 
38 import java.io.ByteArrayOutputStream;
39 import java.io.FileInputStream;
40 import java.io.IOException;
41 import java.nio.charset.StandardCharsets;
42 import java.security.KeyStore;
43 import java.util.ArrayList;
44 import java.util.List;
45 import java.util.function.BooleanSupplier;
46 import java.util.function.Consumer;
47 import java.util.regex.Matcher;
48 import java.util.regex.Pattern;
49 
50 import javax.crypto.Cipher;
51 import javax.crypto.KeyGenerator;
52 import javax.crypto.SecretKey;
53 
54 public class Utils {
55 
56     private static final String TAG = "BiometricTestUtils";
57     private static final String KEYSTORE_PROVIDER = "AndroidKeyStore";
58 
59     /** adb command for dumping the biometric proto */
60     public static final String DUMPSYS_BIOMETRIC = "dumpsys biometric --proto";
61 
62     /**
63      * Retrieves the current SensorStates.
64      */
65     public interface SensorStatesSupplier {
getSensorStates()66         SensorStates getSensorStates() throws Exception;
67     }
68 
69     /**
70      * The Biometric {@link com.android.server.biometrics.SensorConfig}.
71      * Parsed sensor config. See core/res/res/values/config.xml config_biometric_sensors
72      */
73     public static class SensorConfig {
74         public final int id;
75         public final int modality;
76         public final int strength;
77 
SensorConfig(String config)78         public SensorConfig(String config) {
79             String[] elems = config.split(":");
80             id = Integer.parseInt(elems[0]);
81             modality = Integer.parseInt(elems[1]);
82             strength = Integer.parseInt(elems[2]);
83         }
84     }
85 
86     /** Waits for the service to become idle. */
waitForIdleService()87     public static void waitForIdleService() throws Exception {
88         waitForIdleService(() -> getBiometricServiceCurrentState().mSensorStates);
89     }
90 
91     /**
92      * Waits for the service to become idle
93      * @throws Exception
94      */
waitForIdleService(@onNull SensorStatesSupplier supplier)95     public static void waitForIdleService(@NonNull SensorStatesSupplier supplier) throws Exception {
96         for (int i = 0; i < 20; i++) {
97             if (!supplier.getSensorStates().areAllSensorsIdle()) {
98                 Log.d(TAG, "Not idle yet..");
99                 Thread.sleep(300);
100             } else {
101                 return;
102             }
103         }
104         Log.d(TAG, "Timed out waiting for idle");
105     }
106 
107 
108     /** Waits for the specified sensor to become non-idle. */
waitForBusySensor(int sensorId)109     public static void waitForBusySensor(int sensorId) throws Exception {
110         waitForBusySensor(sensorId, () -> getBiometricServiceCurrentState().mSensorStates);
111     }
112 
113     /**
114      * Waits for the specified sensor to become non-idle
115      */
waitForBusySensor(int sensorId, @NonNull SensorStatesSupplier supplier)116     public static void waitForBusySensor(int sensorId, @NonNull SensorStatesSupplier supplier)
117             throws Exception {
118         for (int i = 0; i < 10; i++) {
119             if (!supplier.getSensorStates().sensorStates.get(sensorId).isBusy()) {
120                 Log.d(TAG, "Not busy yet..");
121                 Thread.sleep(300);
122             } else {
123                 return;
124             }
125         }
126         Log.d(TAG, "Timed out waiting to become busy");
127     }
128 
waitFor(@onNull String message, @NonNull BooleanSupplier condition)129     public static void waitFor(@NonNull String message, @NonNull BooleanSupplier condition) {
130         waitFor(message, condition, null /* onFailure */);
131     }
132 
waitFor(@onNull String message, @NonNull BooleanSupplier condition, @Nullable Consumer<Object> onFailure)133     public static void waitFor(@NonNull String message, @NonNull BooleanSupplier condition,
134             @Nullable Consumer<Object> onFailure) {
135         Condition.waitFor(new Condition<>(message, condition)
136                 .setRetryIntervalMs(500)
137                 .setRetryLimit(20)
138                 .setOnFailure(onFailure));
139     }
140 
141     /**
142      * Retrieves the current states of all biometric sensor services (e.g. FingerprintService,
143      * FaceService, etc).
144      *
145      * Note that the states are retrieved from BiometricService, instead of individual services.
146      * This is because 1) BiometricService is the source of truth for all public API-facing things,
147      * and 2) This to include other information, such as UI states, etc as well.
148      */
149     @NonNull
getBiometricServiceCurrentState()150     public static BiometricServiceState getBiometricServiceCurrentState() throws Exception {
151         final byte[] dump = Utils.executeShellCommand(DUMPSYS_BIOMETRIC);
152         final BiometricServiceStateProto proto = BiometricServiceStateProto.parseFrom(dump);
153         return BiometricServiceState.parseFrom(proto);
154     }
155 
156     /**
157      * Runs a shell command, similar to running "adb shell ..." from the command line.
158      * @param cmd A command, without the preceding "adb shell" portion. For example,
159      *            passing in "dumpsys fingerprint" would be the equivalent of running
160      *            "adb shell dumpsys fingerprint" from the command line.
161      * @return The result of the command.
162      */
executeShellCommand(String cmd)163     public static byte[] executeShellCommand(String cmd) {
164         Log.d(TAG, "execute: " + cmd);
165         try {
166             ParcelFileDescriptor pfd = getInstrumentation().getUiAutomation()
167                     .executeShellCommand(cmd);
168             byte[] buf = new byte[512];
169             int bytesRead;
170             FileInputStream fis = new ParcelFileDescriptor.AutoCloseInputStream(pfd);
171             ByteArrayOutputStream stdout = new ByteArrayOutputStream();
172             while ((bytesRead = fis.read(buf)) != -1) {
173                 stdout.write(buf, 0, bytesRead);
174             }
175             fis.close();
176             return stdout.toByteArray();
177         } catch (IOException e) {
178             throw new RuntimeException(e);
179         }
180     }
181 
forceStopActivity(ComponentName componentName)182     public static void forceStopActivity(ComponentName componentName) {
183         executeShellCommand("am force-stop " + componentName.getPackageName()
184                 + " " + componentName.getShortClassName().replaceAll("\\.", ""));
185     }
186 
numberOfSpecifiedOperations(@onNull BiometricServiceState state, int sensorId, int operation)187     public static int numberOfSpecifiedOperations(@NonNull BiometricServiceState state,
188             int sensorId, int operation) {
189         int count = 0;
190         final List<Integer> recentOps = state.mSensorStates.sensorStates.get(sensorId)
191                 .getSchedulerState().getRecentOperations();
192         for (Integer i : recentOps) {
193             if (i == operation) {
194                 count++;
195             }
196         }
197         return count;
198     }
199 
createTimeBoundSecretKey_deprecated(String keyName, boolean useStrongBox)200     public static void createTimeBoundSecretKey_deprecated(String keyName, boolean useStrongBox)
201             throws Exception {
202         KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
203         keyStore.load(null);
204         KeyGenerator keyGenerator = KeyGenerator.getInstance(
205                 KeyProperties.KEY_ALGORITHM_AES, "AndroidKeyStore");
206 
207         // Set the alias of the entry in Android KeyStore where the key will appear
208         // and the constrains (purposes) in the constructor of the Builder
209         keyGenerator.init(new KeyGenParameterSpec.Builder(keyName,
210                 KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
211                 .setBlockModes(KeyProperties.BLOCK_MODE_CBC)
212                 .setUserAuthenticationRequired(true)
213                 .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_PKCS7)
214                 .setIsStrongBoxBacked(useStrongBox)
215                 .setUserAuthenticationValidityDurationSeconds(5 /* seconds */)
216                 .build());
217         keyGenerator.generateKey();
218     }
219 
createTimeBoundSecretKey(String keyName, int authTypes, boolean useStrongBox)220     static void createTimeBoundSecretKey(String keyName, int authTypes, boolean useStrongBox)
221             throws Exception {
222         KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
223         keyStore.load(null);
224         KeyGenerator keyGenerator = KeyGenerator.getInstance(
225                 KeyProperties.KEY_ALGORITHM_AES, "AndroidKeyStore");
226 
227         // Set the alias of the entry in Android KeyStore where the key will appear
228         // and the constrains (purposes) in the constructor of the Builder
229         keyGenerator.init(new KeyGenParameterSpec.Builder(keyName,
230                 KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
231                 .setBlockModes(KeyProperties.BLOCK_MODE_CBC)
232                 .setUserAuthenticationRequired(true)
233                 .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_PKCS7)
234                 .setIsStrongBoxBacked(useStrongBox)
235                 .setUserAuthenticationParameters(1 /* seconds */, authTypes)
236                 .build());
237         keyGenerator.generateKey();
238     }
239 
generateBiometricBoundKey(String keyName, boolean useStrongBox)240     public static void generateBiometricBoundKey(String keyName, boolean useStrongBox)
241             throws Exception {
242         final KeyStore keystore = KeyStore.getInstance(KEYSTORE_PROVIDER);
243         keystore.load(null);
244         KeyGenParameterSpec.Builder builder = new KeyGenParameterSpec.Builder(
245                 keyName,
246                 KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
247                 .setBlockModes(KeyProperties.BLOCK_MODE_CBC)
248                 .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_PKCS7)
249                 .setUserAuthenticationRequired(true)
250                 .setInvalidatedByBiometricEnrollment(true)
251                 .setIsStrongBoxBacked(useStrongBox)
252                 .setUserAuthenticationParameters(0, KeyProperties.AUTH_BIOMETRIC_STRONG);
253 
254         KeyGenerator keyGenerator = KeyGenerator
255                 .getInstance(KeyProperties.KEY_ALGORITHM_AES, KEYSTORE_PROVIDER);
256         keyGenerator.init(builder.build());
257 
258         // Generates and stores the key in Android KeyStore under the keystoreAlias (keyName)
259         // specified in the builder.
260         keyGenerator.generateKey();
261     }
262 
initializeCryptoObject(String keyName)263     public static BiometricPrompt.CryptoObject initializeCryptoObject(String keyName)
264             throws Exception {
265         final KeyStore keystore = KeyStore.getInstance(KEYSTORE_PROVIDER);
266         keystore.load(null);
267         final SecretKey secretKey = (SecretKey) keystore.getKey(
268                 keyName, null /* password */);
269         final Cipher cipher = Cipher.getInstance(KeyProperties.KEY_ALGORITHM_AES + "/"
270                 + KeyProperties.BLOCK_MODE_CBC + "/"
271                 + KeyProperties.ENCRYPTION_PADDING_PKCS7);
272         cipher.init(Cipher.ENCRYPT_MODE, secretKey);
273 
274         final BiometricPrompt.CryptoObject cryptoObject =
275                 new BiometricPrompt.CryptoObject(cipher);
276         return cryptoObject;
277     }
278 
isPublicAuthenticatorConstant(int authenticator)279     public static boolean isPublicAuthenticatorConstant(int authenticator) {
280         switch (authenticator) {
281             case BiometricManager.Authenticators.BIOMETRIC_STRONG:
282             case BiometricManager.Authenticators.BIOMETRIC_WEAK:
283             case BiometricManager.Authenticators.DEVICE_CREDENTIAL:
284                 return true;
285             default:
286                 return false;
287         }
288     }
289 
testApiStrengthToAuthenticatorStrength(int testApiStrength)290     public static int testApiStrengthToAuthenticatorStrength(int testApiStrength) {
291         switch (testApiStrength) {
292             case SensorProperties.STRENGTH_STRONG:
293                 return BiometricManager.Authenticators.BIOMETRIC_STRONG;
294             case SensorProperties.STRENGTH_WEAK:
295                 return BiometricManager.Authenticators.BIOMETRIC_WEAK;
296             default:
297                 throw new IllegalArgumentException("Unable to convert testApiStrength: "
298                         + testApiStrength);
299         }
300     }
301 
isFirstApiLevel29orGreater()302     public static boolean isFirstApiLevel29orGreater() {
303         int firstApiLevel = SystemProperties.getInt("ro.product.first_api_level", 0);
304         if (firstApiLevel >= 29) {
305             return true;
306         }
307         return false;
308     }
309 
310     /** Find the sensor id of the AIDL fingerprint HAL, or -1 if not present. */
getAidlFingerprintSensorId()311     public static int getAidlFingerprintSensorId() {
312         return getAidlSensorId("dumpsys fingerprint", ", provider: FingerprintProvider");
313     }
314 
315     /** Find all of the sensor ids of the AIDL fingerprint HALs */
getAidlFingerprintSensorIds()316     public static List<Integer> getAidlFingerprintSensorIds() {
317         return getAidlSensorIds("dumpsys fingerprint", ", provider: FingerprintProvider");
318     }
319 
320     /** Find the sensor id of the AIDL face HAL, or -1 if not present. */
getAidlFaceSensorId()321     public static int getAidlFaceSensorId() {
322         return getAidlSensorId("dumpsys face", ", provider: FaceProvider");
323     }
324 
325     /** Find current user id. */
getUserId()326     public static int getUserId() {
327         return Process.myUserHandle().getIdentifier();
328     }
329 
getAidlSensorId(String adbCommand, String providerRegex)330     private static int getAidlSensorId(String adbCommand, String providerRegex) {
331         List<Integer> ids = getAidlSensorIds(adbCommand, providerRegex);
332 
333         if (ids.isEmpty()) {
334             return -1;
335         }
336 
337         return ids.get(0);
338     }
339 
getAidlSensorIds(String adbCommand, String providerRegex)340     private static List<Integer> getAidlSensorIds(String adbCommand, String providerRegex) {
341         final byte[] dump = executeShellCommand(adbCommand);
342         final String fpsDumpSys = new String(dump, StandardCharsets.UTF_8);
343         final Matcher matcher =
344                 Pattern.compile("sensorId: (\\d+)" + providerRegex).matcher(fpsDumpSys);
345 
346         final List<Integer> ids = new ArrayList<>();
347         while (matcher.find()) {
348             ids.add(Integer.parseInt(matcher.group(1)));
349         }
350 
351         return ids;
352     }
353 }
354