blob: 996f0fd3a55f756726a5fc7fd5338fdbe89c99dd [file] [log] [blame]
/*
* 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.Manifest.permission.USE_BIOMETRIC_INTERNAL;
import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND_SERVICE;
import static android.hardware.biometrics.BiometricManager.Authenticators;
import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT;
import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW;
import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN;
import static com.android.server.biometrics.PreAuthInfo.AUTHENTICATOR_OK;
import static com.android.server.biometrics.PreAuthInfo.BIOMETRIC_DISABLED_BY_DEVICE_POLICY;
import static com.android.server.biometrics.PreAuthInfo.BIOMETRIC_HARDWARE_NOT_DETECTED;
import static com.android.server.biometrics.PreAuthInfo.BIOMETRIC_INSUFFICIENT_STRENGTH;
import static com.android.server.biometrics.PreAuthInfo.BIOMETRIC_INSUFFICIENT_STRENGTH_AFTER_DOWNGRADE;
import static com.android.server.biometrics.PreAuthInfo.BIOMETRIC_LOCKOUT_PERMANENT;
import static com.android.server.biometrics.PreAuthInfo.BIOMETRIC_LOCKOUT_TIMED;
import static com.android.server.biometrics.PreAuthInfo.BIOMETRIC_NOT_ENABLED_FOR_APPS;
import static com.android.server.biometrics.PreAuthInfo.BIOMETRIC_NOT_ENROLLED;
import static com.android.server.biometrics.PreAuthInfo.BIOMETRIC_NO_HARDWARE;
import static com.android.server.biometrics.PreAuthInfo.CREDENTIAL_NOT_ENROLLED;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ActivityManager;
import android.content.ComponentName;
import android.content.Context;
import android.content.pm.PackageManager;
import android.hardware.biometrics.BiometricAuthenticator;
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.IBiometricService;
import android.hardware.biometrics.PromptInfo;
import android.hardware.biometrics.SensorProperties;
import android.hardware.biometrics.SensorPropertiesInternal;
import android.hardware.fingerprint.IUdfpsOverlayController;
import android.os.Binder;
import android.os.Build;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.UserHandle;
import android.os.UserManager;
import android.provider.Settings;
import android.util.Slog;
import com.android.internal.R;
import com.android.internal.widget.LockPatternUtils;
import com.android.server.biometrics.sensors.AuthenticationClient;
import com.android.server.biometrics.sensors.BaseClientMonitor;
import java.util.List;
public class Utils {
private static final String TAG = "BiometricUtils";
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 PromptInfo#setDeviceCredentialAllowed(boolean)} with
* {@link PromptInfo#setAuthenticators(int)}, as the former is not flexible enough.
*/
static void combineAuthenticatorBundles(PromptInfo promptInfo) {
// Cache and remove explicit ALLOW_DEVICE_CREDENTIAL boolean flag from the bundle.
final boolean deviceCredentialAllowed = promptInfo.isDeviceCredentialAllowed();
promptInfo.setDeviceCredentialAllowed(false);
final @Authenticators.Types int authenticators;
if (promptInfo.getAuthenticators() != 0) {
// Ignore ALLOW_DEVICE_CREDENTIAL flag if AUTH_TYPES_ALLOWED is defined.
authenticators = promptInfo.getAuthenticators();
} else {
// Otherwise, use ALLOW_DEVICE_CREDENTIAL flag along with Weak+ biometrics by default.
authenticators = deviceCredentialAllowed
? Authenticators.DEVICE_CREDENTIAL | Authenticators.BIOMETRIC_WEAK
: Authenticators.BIOMETRIC_WEAK;
}
promptInfo.setAuthenticators(authenticators);
}
/**
* @param authenticators composed of one or more values from {@link Authenticators}
* @return true if device credential is allowed.
*/
static boolean isCredentialRequested(@Authenticators.Types int authenticators) {
return (authenticators & Authenticators.DEVICE_CREDENTIAL) != 0;
}
/**
* @param promptInfo should be first processed by
* {@link #combineAuthenticatorBundles(PromptInfo)}
* @return true if device credential is allowed.
*/
static boolean isCredentialRequested(PromptInfo promptInfo) {
return isCredentialRequested(promptInfo.getAuthenticators());
}
/**
* 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.
*/
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 promptInfo should be first processed by
* {@link #combineAuthenticatorBundles(PromptInfo)}
* @return minimal allowed biometric strength or 0 if biometric authentication is not allowed.
*/
static int getPublicBiometricStrength(PromptInfo promptInfo) {
return getPublicBiometricStrength(promptInfo.getAuthenticators());
}
/**
* Checks if any of the publicly defined strengths are set.
*
* @param authenticators composed of one or more values from {@link Authenticators}
* @return true if biometric authentication is allowed.
*/
static boolean isBiometricRequested(@Authenticators.Types int authenticators) {
return getPublicBiometricStrength(authenticators) != 0;
}
/**
* Checks if any of the publicly defined strengths are set.
*
* @param promptInfo should be first processed by
* {@link #combineAuthenticatorBundles(PromptInfo)}
* @return true if biometric authentication is allowed.
*/
static boolean isBiometricRequested(PromptInfo promptInfo) {
return getPublicBiometricStrength(promptInfo) != 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(@Authenticators.Types int sensorStrength,
@Authenticators.Types int requestedStrength) {
// Clear out any bits that are not reserved for biometric
sensorStrength &= Authenticators.BIOMETRIC_MIN_STRENGTH;
// If the authenticator contains bits outside of the requested strength, it is too weak.
if ((sensorStrength & ~requestedStrength) != 0) {
return false;
}
for (int i = Authenticators.BIOMETRIC_MAX_STRENGTH;
i <= requestedStrength; i = (i << 1) | 1) {
if (i == sensorStrength) {
return true;
}
}
Slog.e(BiometricService.TAG, "Unknown sensorStrength: " + sensorStrength
+ ", requestedStrength: " + requestedStrength);
return false;
}
/**
* Checks if the authenticator configuration is a valid combination of the public APIs
* @param promptInfo
* @return
*/
static boolean isValidAuthenticatorConfig(PromptInfo promptInfo) {
final int authenticators = promptInfo.getAuthenticators();
return isValidAuthenticatorConfig(authenticators);
}
/**
* Checks if the authenticator configuration is a valid combination of the public APIs
* @param authenticators
* @return
*/
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
&& isCredentialRequested(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}
*/
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;
case BiometricConstants.BIOMETRIC_ERROR_SECURITY_UPDATE_REQUIRED:
biometricManagerCode = BiometricManager.BIOMETRIC_ERROR_SECURITY_UPDATE_REQUIRED;
break;
case BiometricConstants.BIOMETRIC_ERROR_LOCKOUT:
case BiometricConstants.BIOMETRIC_ERROR_LOCKOUT_PERMANENT:
biometricManagerCode = BiometricManager.BIOMETRIC_SUCCESS;
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.
*/
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);
}
}
static int authenticatorStatusToBiometricConstant(
@PreAuthInfo.AuthenticatorStatus int status) {
switch (status) {
case BIOMETRIC_NO_HARDWARE:
case BIOMETRIC_INSUFFICIENT_STRENGTH:
return BiometricConstants.BIOMETRIC_ERROR_HW_NOT_PRESENT;
case AUTHENTICATOR_OK:
return BiometricConstants.BIOMETRIC_SUCCESS;
case BIOMETRIC_INSUFFICIENT_STRENGTH_AFTER_DOWNGRADE:
return BiometricConstants.BIOMETRIC_ERROR_SECURITY_UPDATE_REQUIRED;
case BIOMETRIC_NOT_ENROLLED:
return BiometricConstants.BIOMETRIC_ERROR_NO_BIOMETRICS;
case CREDENTIAL_NOT_ENROLLED:
return BiometricConstants.BIOMETRIC_ERROR_NO_DEVICE_CREDENTIAL;
case BIOMETRIC_LOCKOUT_TIMED:
return BiometricConstants.BIOMETRIC_ERROR_LOCKOUT;
case BIOMETRIC_LOCKOUT_PERMANENT:
return BiometricConstants.BIOMETRIC_ERROR_LOCKOUT_PERMANENT;
case BIOMETRIC_DISABLED_BY_DEVICE_POLICY:
case BIOMETRIC_HARDWARE_NOT_DETECTED:
case BIOMETRIC_NOT_ENABLED_FOR_APPS:
default:
return BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE;
}
}
static boolean isConfirmationSupported(@BiometricAuthenticator.Modality int modality) {
switch (modality) {
case BiometricAuthenticator.TYPE_FACE:
case BiometricAuthenticator.TYPE_IRIS:
return true;
default:
return false;
}
}
static int removeBiometricBits(@Authenticators.Types int authenticators) {
return authenticators & ~Authenticators.BIOMETRIC_MIN_STRENGTH;
}
public static boolean listContains(int[] haystack, int needle) {
for (int i = 0; i < haystack.length; i++) {
if (haystack[i] == needle) {
return true;
}
}
return false;
}
public static void checkPermission(Context context, String permission) {
context.enforceCallingOrSelfPermission(permission,
"Must have " + permission + " permission.");
}
public static boolean isCurrentUserOrProfile(Context context, int userId) {
UserManager um = UserManager.get(context);
if (um == null) {
Slog.e(TAG, "Unable to get UserManager");
return false;
}
final long token = Binder.clearCallingIdentity();
try {
// Allow current user or profiles of the current user...
for (int profileId : um.getEnabledProfileIds(ActivityManager.getCurrentUser())) {
if (profileId == userId) {
return true;
}
}
} finally {
Binder.restoreCallingIdentity(token);
}
return false;
}
public static boolean isStrongBiometric(int sensorId) {
IBiometricService service = IBiometricService.Stub.asInterface(
ServiceManager.getService(Context.BIOMETRIC_SERVICE));
try {
return Utils.isAtLeastStrength(service.getCurrentStrength(sensorId),
Authenticators.BIOMETRIC_STRONG);
} catch (RemoteException e) {
Slog.e(TAG, "RemoteException", e);
return false;
}
}
/**
* Returns the sensor's current strength, taking any updated strengths into effect.
*
* @param sensorId The sensor Id
* @return see {@link BiometricManager.Authenticators}
*/
public static @Authenticators.Types int getCurrentStrength(int sensorId) {
IBiometricService service = IBiometricService.Stub.asInterface(
ServiceManager.getService(Context.BIOMETRIC_SERVICE));
try {
return service.getCurrentStrength(sensorId);
} catch (RemoteException e) {
Slog.e(TAG, "RemoteException", e);
return Authenticators.EMPTY_SET;
}
}
/**
* Checks if a client package matches Keyguard and can perform internal biometric operations.
*
* @param context The system context.
* @param clientPackage The name of the package to be checked against Keyguard.
* @return Whether the given package matches Keyguard.
*/
public static boolean isKeyguard(@NonNull Context context, @Nullable String clientPackage) {
final boolean hasPermission = hasInternalPermission(context);
final ComponentName keyguardComponent = ComponentName.unflattenFromString(
context.getResources().getString(R.string.config_keyguardComponent));
final String keyguardPackage = keyguardComponent != null
? keyguardComponent.getPackageName() : null;
return hasPermission && keyguardPackage != null && keyguardPackage.equals(clientPackage);
}
/**
* Checks if a client package matches the Android system and can perform internal biometric
* operations.
*
* @param context The system context.
* @param clientPackage The name of the package to be checked against the Android system.
* @return Whether the given package matches the Android system.
*/
public static boolean isSystem(@NonNull Context context, @Nullable String clientPackage) {
return hasInternalPermission(context) && "android".equals(clientPackage);
}
/**
* Checks if a client package matches Settings and can perform internal biometric operations.
*
* @param context The system context.
* @param clientPackage The name of the package to be checked against Settings.
* @return Whether the given package matches Settings.
*/
public static boolean isSettings(@NonNull Context context, @Nullable String clientPackage) {
return hasInternalPermission(context) && "com.android.settings".equals(clientPackage);
}
private static boolean hasInternalPermission(@NonNull Context context) {
return context.checkCallingOrSelfPermission(USE_BIOMETRIC_INTERNAL)
== PackageManager.PERMISSION_GRANTED;
}
public static String getClientName(@Nullable BaseClientMonitor client) {
return client != null ? client.getClass().getSimpleName() : "null";
}
private static boolean containsFlag(int haystack, int needle) {
return (haystack & needle) != 0;
}
public static boolean isUserEncryptedOrLockdown(@NonNull LockPatternUtils lpu, int user) {
final int strongAuth = lpu.getStrongAuthForUser(user);
final boolean isEncrypted = containsFlag(strongAuth, STRONG_AUTH_REQUIRED_AFTER_BOOT);
final boolean isLockDown = containsFlag(strongAuth, STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW)
|| containsFlag(strongAuth, STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN);
Slog.d(TAG, "isEncrypted: " + isEncrypted + " isLockdown: " + isLockDown);
return isEncrypted || isLockDown;
}
public static boolean isForeground(int callingUid, int callingPid) {
try {
final List<ActivityManager.RunningAppProcessInfo> procs =
ActivityManager.getService().getRunningAppProcesses();
if (procs == null) {
Slog.e(TAG, "No running app processes found, defaulting to true");
return true;
}
for (int i = 0; i < procs.size(); i++) {
ActivityManager.RunningAppProcessInfo proc = procs.get(i);
if (proc.pid == callingPid && proc.uid == callingUid
&& proc.importance <= IMPORTANCE_FOREGROUND_SERVICE) {
return true;
}
}
} catch (RemoteException e) {
Slog.w(TAG, "am.getRunningAppProcesses() failed");
}
return false;
}
/**
* Converts from {@link BiometricManager.Authenticators} biometric strength to the internal
* {@link SensorPropertiesInternal} strength.
*/
public static @SensorProperties.Strength int authenticatorStrengthToPropertyStrength(
@Authenticators.Types int strength) {
switch (strength) {
case BiometricManager.Authenticators.BIOMETRIC_CONVENIENCE:
return SensorProperties.STRENGTH_CONVENIENCE;
case BiometricManager.Authenticators.BIOMETRIC_WEAK:
return SensorProperties.STRENGTH_WEAK;
case BiometricManager.Authenticators.BIOMETRIC_STRONG:
return SensorProperties.STRENGTH_STRONG;
default:
throw new IllegalArgumentException("Unknown strength: " + strength);
}
}
public static @Authenticators.Types int propertyStrengthToAuthenticatorStrength(
@SensorProperties.Strength int strength) {
switch (strength) {
case SensorProperties.STRENGTH_CONVENIENCE:
return Authenticators.BIOMETRIC_CONVENIENCE;
case SensorProperties.STRENGTH_WEAK:
return Authenticators.BIOMETRIC_WEAK;
case SensorProperties.STRENGTH_STRONG:
return Authenticators.BIOMETRIC_STRONG;
default:
throw new IllegalArgumentException("Unknown strength: " + strength);
}
}
public static int getUdfpsAuthReason(@NonNull AuthenticationClient<?> client) {
if (client.isKeyguard()) {
return IUdfpsOverlayController.REASON_AUTH_FPM_KEYGUARD;
} else if (client.isBiometricPrompt()) {
return IUdfpsOverlayController.REASON_AUTH_BP;
} else {
return IUdfpsOverlayController.REASON_AUTH_FPM_OTHER;
}
}
}