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