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.SystemProperties;
26 import android.os.ParcelFileDescriptor;
27 import android.security.keystore.KeyGenParameterSpec;
28 import android.security.keystore.KeyProperties;
29 import android.server.wm.Condition;
30 import android.util.Log;
31 
32 import androidx.annotation.NonNull;
33 import androidx.annotation.Nullable;
34 
35 import com.android.server.biometrics.nano.BiometricServiceStateProto;
36 
37 import java.io.ByteArrayOutputStream;
38 import java.io.FileInputStream;
39 import java.io.IOException;
40 import java.security.KeyStore;
41 import java.util.List;
42 import java.util.function.BooleanSupplier;
43 import java.util.function.Consumer;
44 
45 import javax.crypto.Cipher;
46 import javax.crypto.KeyGenerator;
47 import javax.crypto.SecretKey;
48 
49 public class Utils {
50 
51     private static final String TAG = "BiometricTestUtils";
52     private static final String KEYSTORE_PROVIDER = "AndroidKeyStore";
53 
54     /** adb command for dumping the biometric proto */
55     public static final String DUMPSYS_BIOMETRIC = "dumpsys biometric --proto";
56 
57     /**
58      * Retrieves the current SensorStates.
59      */
60     public interface SensorStatesSupplier {
getSensorStates()61         SensorStates getSensorStates() throws Exception;
62     }
63 
64     /**
65      * Waits for the service to become idle
66      * @throws Exception
67      */
waitForIdleService(@onNull SensorStatesSupplier supplier)68     public static void waitForIdleService(@NonNull SensorStatesSupplier supplier) throws Exception {
69         for (int i = 0; i < 10; i++) {
70             if (!supplier.getSensorStates().areAllSensorsIdle()) {
71                 Log.d(TAG, "Not idle yet..");
72                 Thread.sleep(300);
73             } else {
74                 return;
75             }
76         }
77         Log.d(TAG, "Timed out waiting for idle");
78     }
79 
80     /**
81      * Waits for the specified sensor to become non-idle
82      */
waitForBusySensor(int sensorId, @NonNull SensorStatesSupplier supplier)83     public static void waitForBusySensor(int sensorId, @NonNull SensorStatesSupplier supplier)
84             throws Exception {
85         for (int i = 0; i < 10; i++) {
86             if (!supplier.getSensorStates().sensorStates.get(sensorId).isBusy()) {
87                 Log.d(TAG, "Not busy yet..");
88                 Thread.sleep(300);
89             } else {
90                 return;
91             }
92         }
93         Log.d(TAG, "Timed out waiting to become busy");
94     }
95 
waitFor(@onNull String message, @NonNull BooleanSupplier condition)96     public static void waitFor(@NonNull String message, @NonNull BooleanSupplier condition) {
97         waitFor(message, condition, null /* onFailure */);
98     }
99 
waitFor(@onNull String message, @NonNull BooleanSupplier condition, @Nullable Consumer<Object> onFailure)100     public static void waitFor(@NonNull String message, @NonNull BooleanSupplier condition,
101             @Nullable Consumer<Object> onFailure) {
102         Condition.waitFor(new Condition<>(message, condition)
103                 .setRetryIntervalMs(500)
104                 .setRetryLimit(20)
105                 .setOnFailure(onFailure));
106     }
107 
108     /**
109      * Retrieves the current states of all biometric sensor services (e.g. FingerprintService,
110      * FaceService, etc).
111      *
112      * Note that the states are retrieved from BiometricService, instead of individual services.
113      * This is because 1) BiometricService is the source of truth for all public API-facing things,
114      * and 2) This to include other information, such as UI states, etc as well.
115      */
116     @NonNull
getBiometricServiceCurrentState()117     public static BiometricServiceState getBiometricServiceCurrentState() throws Exception {
118         final byte[] dump = Utils.executeShellCommand(DUMPSYS_BIOMETRIC);
119         final BiometricServiceStateProto proto = BiometricServiceStateProto.parseFrom(dump);
120         return BiometricServiceState.parseFrom(proto);
121     }
122 
123     /**
124      * Runs a shell command, similar to running "adb shell ..." from the command line.
125      * @param cmd A command, without the preceding "adb shell" portion. For example,
126      *            passing in "dumpsys fingerprint" would be the equivalent of running
127      *            "adb shell dumpsys fingerprint" from the command line.
128      * @return The result of the command.
129      */
executeShellCommand(String cmd)130     public static byte[] executeShellCommand(String cmd) {
131         Log.d(TAG, "execute: " + cmd);
132         try {
133             ParcelFileDescriptor pfd = getInstrumentation().getUiAutomation()
134                     .executeShellCommand(cmd);
135             byte[] buf = new byte[512];
136             int bytesRead;
137             FileInputStream fis = new ParcelFileDescriptor.AutoCloseInputStream(pfd);
138             ByteArrayOutputStream stdout = new ByteArrayOutputStream();
139             while ((bytesRead = fis.read(buf)) != -1) {
140                 stdout.write(buf, 0, bytesRead);
141             }
142             fis.close();
143             return stdout.toByteArray();
144         } catch (IOException e) {
145             throw new RuntimeException(e);
146         }
147     }
148 
forceStopActivity(ComponentName componentName)149     public static void forceStopActivity(ComponentName componentName) {
150         executeShellCommand("am force-stop " + componentName.getPackageName()
151                 + " " + componentName.getShortClassName().replaceAll("\\.", ""));
152     }
153 
numberOfSpecifiedOperations(@onNull BiometricServiceState state, int sensorId, int operation)154     public static int numberOfSpecifiedOperations(@NonNull BiometricServiceState state,
155             int sensorId, int operation) {
156         int count = 0;
157         final List<Integer> recentOps = state.mSensorStates.sensorStates.get(sensorId)
158                 .getSchedulerState().getRecentOperations();
159         for (Integer i : recentOps) {
160             if (i == operation) {
161                 count++;
162             }
163         }
164         return count;
165     }
166 
createTimeBoundSecretKey_deprecated(String keyName, boolean useStrongBox)167     public static void createTimeBoundSecretKey_deprecated(String keyName, boolean useStrongBox)
168             throws Exception {
169         KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
170         keyStore.load(null);
171         KeyGenerator keyGenerator = KeyGenerator.getInstance(
172                 KeyProperties.KEY_ALGORITHM_AES, "AndroidKeyStore");
173 
174         // Set the alias of the entry in Android KeyStore where the key will appear
175         // and the constrains (purposes) in the constructor of the Builder
176         keyGenerator.init(new KeyGenParameterSpec.Builder(keyName,
177                 KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
178                 .setBlockModes(KeyProperties.BLOCK_MODE_CBC)
179                 .setUserAuthenticationRequired(true)
180                 .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_PKCS7)
181                 .setIsStrongBoxBacked(useStrongBox)
182                 .setUserAuthenticationValidityDurationSeconds(5 /* seconds */)
183                 .build());
184         keyGenerator.generateKey();
185     }
186 
createTimeBoundSecretKey(String keyName, int authTypes, boolean useStrongBox)187     static void createTimeBoundSecretKey(String keyName, int authTypes, boolean useStrongBox)
188             throws Exception {
189         KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
190         keyStore.load(null);
191         KeyGenerator keyGenerator = KeyGenerator.getInstance(
192                 KeyProperties.KEY_ALGORITHM_AES, "AndroidKeyStore");
193 
194         // Set the alias of the entry in Android KeyStore where the key will appear
195         // and the constrains (purposes) in the constructor of the Builder
196         keyGenerator.init(new KeyGenParameterSpec.Builder(keyName,
197                 KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
198                 .setBlockModes(KeyProperties.BLOCK_MODE_CBC)
199                 .setUserAuthenticationRequired(true)
200                 .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_PKCS7)
201                 .setIsStrongBoxBacked(useStrongBox)
202                 .setUserAuthenticationParameters(1 /* seconds */, authTypes)
203                 .build());
204         keyGenerator.generateKey();
205     }
206 
generateBiometricBoundKey(String keyName, boolean useStrongBox)207     public static void generateBiometricBoundKey(String keyName, boolean useStrongBox)
208             throws Exception {
209         final KeyStore keystore = KeyStore.getInstance(KEYSTORE_PROVIDER);
210         keystore.load(null);
211         KeyGenParameterSpec.Builder builder = new KeyGenParameterSpec.Builder(
212                 keyName,
213                 KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
214                 .setBlockModes(KeyProperties.BLOCK_MODE_CBC)
215                 .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_PKCS7)
216                 .setUserAuthenticationRequired(true)
217                 .setInvalidatedByBiometricEnrollment(true)
218                 .setIsStrongBoxBacked(useStrongBox)
219                 .setUserAuthenticationParameters(0, KeyProperties.AUTH_BIOMETRIC_STRONG);
220 
221         KeyGenerator keyGenerator = KeyGenerator
222                 .getInstance(KeyProperties.KEY_ALGORITHM_AES, KEYSTORE_PROVIDER);
223         keyGenerator.init(builder.build());
224 
225         // Generates and stores the key in Android KeyStore under the keystoreAlias (keyName)
226         // specified in the builder.
227         keyGenerator.generateKey();
228     }
229 
initializeCryptoObject(String keyName)230     public static BiometricPrompt.CryptoObject initializeCryptoObject(String keyName)
231             throws Exception {
232         final KeyStore keystore = KeyStore.getInstance(KEYSTORE_PROVIDER);
233         keystore.load(null);
234         final SecretKey secretKey = (SecretKey) keystore.getKey(
235                 keyName, null /* password */);
236         final Cipher cipher = Cipher.getInstance(KeyProperties.KEY_ALGORITHM_AES + "/"
237                 + KeyProperties.BLOCK_MODE_CBC + "/"
238                 + KeyProperties.ENCRYPTION_PADDING_PKCS7);
239         cipher.init(Cipher.ENCRYPT_MODE, secretKey);
240 
241         final BiometricPrompt.CryptoObject cryptoObject =
242                 new BiometricPrompt.CryptoObject(cipher);
243         return cryptoObject;
244     }
245 
isPublicAuthenticatorConstant(int authenticator)246     public static boolean isPublicAuthenticatorConstant(int authenticator) {
247         switch (authenticator) {
248             case BiometricManager.Authenticators.BIOMETRIC_STRONG:
249             case BiometricManager.Authenticators.BIOMETRIC_WEAK:
250             case BiometricManager.Authenticators.DEVICE_CREDENTIAL:
251                 return true;
252             default:
253                 return false;
254         }
255     }
256 
testApiStrengthToAuthenticatorStrength(int testApiStrength)257     public static int testApiStrengthToAuthenticatorStrength(int testApiStrength) {
258         switch (testApiStrength) {
259             case SensorProperties.STRENGTH_STRONG:
260                 return BiometricManager.Authenticators.BIOMETRIC_STRONG;
261             case SensorProperties.STRENGTH_WEAK:
262                 return BiometricManager.Authenticators.BIOMETRIC_WEAK;
263             default:
264                 throw new IllegalArgumentException("Unable to convert testApiStrength: "
265                         + testApiStrength);
266         }
267     }
268 
isFirstApiLevel29orGreater()269     public static boolean isFirstApiLevel29orGreater() {
270         int firstApiLevel = SystemProperties.getInt("ro.product.first_api_level", 0);
271         if (firstApiLevel >= 29) {
272             return true;
273         }
274         return false;
275     }
276 }
277