| /* |
| * Copyright (C) 2019 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.server.biometrics; |
| |
| import static android.hardware.biometrics.BiometricManager.Authenticators; |
| |
| import android.content.Context; |
| import android.hardware.biometrics.BiometricConstants; |
| import android.hardware.biometrics.BiometricManager; |
| import android.hardware.biometrics.BiometricPrompt; |
| import android.hardware.biometrics.BiometricPrompt.AuthenticationResultType; |
| import android.hardware.biometrics.IBiometricNativeHandle; |
| import android.os.Build; |
| import android.os.Bundle; |
| import android.os.NativeHandle; |
| import android.os.UserHandle; |
| import android.provider.Settings; |
| import android.util.Slog; |
| |
| import java.io.FileDescriptor; |
| import java.io.IOException; |
| |
| public class Utils { |
| public static boolean isDebugEnabled(Context context, int targetUserId) { |
| if (targetUserId == UserHandle.USER_NULL) { |
| return false; |
| } |
| |
| if (!(Build.IS_ENG || Build.IS_USERDEBUG)) { |
| return false; |
| } |
| |
| if (Settings.Secure.getIntForUser(context.getContentResolver(), |
| Settings.Secure.BIOMETRIC_DEBUG_ENABLED, 0, |
| targetUserId) == 0) { |
| return false; |
| } |
| return true; |
| } |
| |
| /** |
| * Combines {@link BiometricPrompt#KEY_ALLOW_DEVICE_CREDENTIAL} with |
| * {@link BiometricPrompt#KEY_AUTHENTICATORS_ALLOWED}, as the former is not flexible enough. |
| */ |
| public static void combineAuthenticatorBundles(Bundle bundle) { |
| // Cache and remove explicit ALLOW_DEVICE_CREDENTIAL boolean flag from the bundle. |
| final boolean deviceCredentialAllowed = |
| bundle.getBoolean(BiometricPrompt.KEY_ALLOW_DEVICE_CREDENTIAL, false); |
| bundle.remove(BiometricPrompt.KEY_ALLOW_DEVICE_CREDENTIAL); |
| |
| final @Authenticators.Types int authenticators; |
| if (bundle.containsKey(BiometricPrompt.KEY_AUTHENTICATORS_ALLOWED)) { |
| // Ignore ALLOW_DEVICE_CREDENTIAL flag if AUTH_TYPES_ALLOWED is defined. |
| authenticators = bundle.getInt(BiometricPrompt.KEY_AUTHENTICATORS_ALLOWED, 0); |
| } else { |
| // Otherwise, use ALLOW_DEVICE_CREDENTIAL flag along with Weak+ biometrics by default. |
| authenticators = deviceCredentialAllowed |
| ? Authenticators.DEVICE_CREDENTIAL | Authenticators.BIOMETRIC_WEAK |
| : Authenticators.BIOMETRIC_WEAK; |
| } |
| |
| bundle.putInt(BiometricPrompt.KEY_AUTHENTICATORS_ALLOWED, authenticators); |
| } |
| |
| /** |
| * @param authenticators composed of one or more values from {@link Authenticators} |
| * @return true if device credential is allowed. |
| */ |
| public static boolean isDeviceCredentialAllowed(@Authenticators.Types int authenticators) { |
| return (authenticators & Authenticators.DEVICE_CREDENTIAL) != 0; |
| } |
| |
| /** |
| * @param bundle should be first processed by {@link #combineAuthenticatorBundles(Bundle)} |
| * @return true if device credential is allowed. |
| */ |
| public static boolean isDeviceCredentialAllowed(Bundle bundle) { |
| return isDeviceCredentialAllowed(bundle.getInt(BiometricPrompt.KEY_AUTHENTICATORS_ALLOWED)); |
| } |
| |
| /** |
| * Checks if any of the publicly defined strengths are set. |
| * |
| * @param authenticators composed of one or more values from {@link Authenticators} |
| * @return minimal allowed biometric strength or 0 if biometric authentication is not allowed. |
| */ |
| public static int getPublicBiometricStrength(@Authenticators.Types int authenticators) { |
| // Only biometrics WEAK and above are allowed to integrate with the public APIs. |
| return authenticators & Authenticators.BIOMETRIC_WEAK; |
| } |
| |
| /** |
| * Checks if any of the publicly defined strengths are set. |
| * |
| * @param bundle should be first processed by {@link #combineAuthenticatorBundles(Bundle)} |
| * @return minimal allowed biometric strength or 0 if biometric authentication is not allowed. |
| */ |
| public static int getPublicBiometricStrength(Bundle bundle) { |
| return getPublicBiometricStrength( |
| bundle.getInt(BiometricPrompt.KEY_AUTHENTICATORS_ALLOWED)); |
| } |
| |
| /** |
| * Checks if any of the publicly defined strengths are set. |
| * |
| * @param bundle should be first processed by {@link #combineAuthenticatorBundles(Bundle)} |
| * @return true if biometric authentication is allowed. |
| */ |
| public static boolean isBiometricAllowed(Bundle bundle) { |
| return getPublicBiometricStrength(bundle) != 0; |
| } |
| |
| /** |
| * @param sensorStrength the strength of the sensor |
| * @param requestedStrength the strength that it must meet |
| * @return true only if the sensor is at least as strong as the requested strength |
| */ |
| public static boolean isAtLeastStrength(int sensorStrength, int requestedStrength) { |
| // If the authenticator contains bits outside of the requested strength, it is too weak. |
| return (~requestedStrength & sensorStrength) == 0; |
| } |
| |
| /** |
| * Checks if the authenticator configuration is a valid combination of the public APIs |
| * @param bundle |
| * @return |
| */ |
| public static boolean isValidAuthenticatorConfig(Bundle bundle) { |
| final int authenticators = bundle.getInt(BiometricPrompt.KEY_AUTHENTICATORS_ALLOWED); |
| return isValidAuthenticatorConfig(authenticators); |
| } |
| |
| /** |
| * Checks if the authenticator configuration is a valid combination of the public APIs |
| * @param authenticators |
| * @return |
| */ |
| public static boolean isValidAuthenticatorConfig(int authenticators) { |
| // The caller is not required to set the authenticators. But if they do, check the below. |
| if (authenticators == 0) { |
| return true; |
| } |
| |
| // Check if any of the non-biometric and non-credential bits are set. If so, this is |
| // invalid. |
| final int testBits = ~(Authenticators.DEVICE_CREDENTIAL |
| | Authenticators.BIOMETRIC_MIN_STRENGTH); |
| if ((authenticators & testBits) != 0) { |
| Slog.e(BiometricService.TAG, "Non-biometric, non-credential bits found." |
| + " Authenticators: " + authenticators); |
| return false; |
| } |
| |
| // Check that biometrics bits are either NONE, WEAK, or STRONG. If NONE, DEVICE_CREDENTIAL |
| // should be set. |
| final int biometricBits = authenticators & Authenticators.BIOMETRIC_MIN_STRENGTH; |
| if (biometricBits == Authenticators.EMPTY_SET |
| && isDeviceCredentialAllowed(authenticators)) { |
| return true; |
| } else if (biometricBits == Authenticators.BIOMETRIC_STRONG) { |
| return true; |
| } else if (biometricBits == Authenticators.BIOMETRIC_WEAK) { |
| return true; |
| } |
| |
| Slog.e(BiometricService.TAG, "Unsupported biometric flags. Authenticators: " |
| + authenticators); |
| // Non-supported biometric flags are being used |
| return false; |
| } |
| |
| /** |
| * Converts error codes from BiometricConstants, which are used in most of the internal plumbing |
| * and eventually returned to {@link BiometricPrompt.AuthenticationCallback} to public |
| * {@link BiometricManager} constants, which are used by APIs such as |
| * {@link BiometricManager#canAuthenticate(int)} |
| * |
| * @param biometricConstantsCode see {@link BiometricConstants} |
| * @return see {@link BiometricManager} |
| */ |
| public static int biometricConstantsToBiometricManager(int biometricConstantsCode) { |
| final int biometricManagerCode; |
| |
| switch (biometricConstantsCode) { |
| case BiometricConstants.BIOMETRIC_SUCCESS: |
| biometricManagerCode = BiometricManager.BIOMETRIC_SUCCESS; |
| break; |
| case BiometricConstants.BIOMETRIC_ERROR_NO_BIOMETRICS: |
| case BiometricConstants.BIOMETRIC_ERROR_NO_DEVICE_CREDENTIAL: |
| biometricManagerCode = BiometricManager.BIOMETRIC_ERROR_NONE_ENROLLED; |
| break; |
| case BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE: |
| biometricManagerCode = BiometricManager.BIOMETRIC_ERROR_HW_UNAVAILABLE; |
| break; |
| case BiometricConstants.BIOMETRIC_ERROR_HW_NOT_PRESENT: |
| biometricManagerCode = BiometricManager.BIOMETRIC_ERROR_NO_HARDWARE; |
| break; |
| default: |
| Slog.e(BiometricService.TAG, "Unhandled result code: " + biometricConstantsCode); |
| biometricManagerCode = BiometricManager.BIOMETRIC_ERROR_HW_UNAVAILABLE; |
| break; |
| } |
| return biometricManagerCode; |
| } |
| |
| /** |
| * Converts a {@link BiometricPrompt} dismissal reason to an authentication type at the level of |
| * granularity supported by {@link BiometricPrompt.AuthenticationResult}. |
| * |
| * @param reason The reason that the {@link BiometricPrompt} was dismissed. Must be one of: |
| * {@link BiometricPrompt#DISMISSED_REASON_CREDENTIAL_CONFIRMED}, |
| * {@link BiometricPrompt#DISMISSED_REASON_BIOMETRIC_CONFIRMED}, or |
| * {@link BiometricPrompt#DISMISSED_REASON_BIOMETRIC_CONFIRM_NOT_REQUIRED} |
| * @return An integer representing the authentication type for {@link |
| * BiometricPrompt.AuthenticationResult}. |
| * @throws IllegalArgumentException if given an invalid dismissal reason. |
| */ |
| public static @AuthenticationResultType int getAuthenticationTypeForResult(int reason) { |
| switch (reason) { |
| case BiometricPrompt.DISMISSED_REASON_CREDENTIAL_CONFIRMED: |
| return BiometricPrompt.AUTHENTICATION_RESULT_TYPE_DEVICE_CREDENTIAL; |
| |
| case BiometricPrompt.DISMISSED_REASON_BIOMETRIC_CONFIRMED: |
| case BiometricPrompt.DISMISSED_REASON_BIOMETRIC_CONFIRM_NOT_REQUIRED: |
| return BiometricPrompt.AUTHENTICATION_RESULT_TYPE_BIOMETRIC; |
| |
| default: |
| throw new IllegalArgumentException("Unsupported dismissal reason: " + reason); |
| } |
| } |
| |
| /** |
| * Converts an {@link IBiometricNativeHandle} to a {@link NativeHandle} by duplicating the |
| * the underlying file descriptors. |
| * |
| * Both the original and new handle must be closed after use. |
| * |
| * @param h {@link IBiometricNativeHandle} received as a binder call argument. Usually used to |
| * identify a WindowManager window. Can be null. |
| * @return A {@link NativeHandle} representation of {@code h}. Will be null if either {@code h} |
| * or its contents are null. |
| */ |
| public static NativeHandle dupNativeHandle(IBiometricNativeHandle h) { |
| NativeHandle handle = null; |
| if (h != null && h.fds != null && h.ints != null) { |
| FileDescriptor[] fds = new FileDescriptor[h.fds.length]; |
| for (int i = 0; i < h.fds.length; ++i) { |
| try { |
| fds[i] = h.fds[i].dup().getFileDescriptor(); |
| } catch (IOException e) { |
| return null; |
| } |
| } |
| handle = new NativeHandle(fds, h.ints, true /* own */); |
| } |
| return handle; |
| } |
| } |