From 8ed6ea1f2668a3dfd9118b5d5154dbc4e8c65bb8 Mon Sep 17 00:00:00 2001 From: Diya Bera Date: Tue, 6 Jun 2023 10:47:52 -0700 Subject: [PATCH] Make face auth ineligible when camera access is disabled Cherry picked I19556fa7f861f2f7655e792b1da9d74d3cf2259e to master due to b/286408867. Code was merged via `-s ours` and is missing even though the sha exists on the target branch. Cherry pick is required to bring in the code. Manually remove the "Merged in" directive to allow for downstream propagation. Test: Disable camera access in Settings, Open biometric prompt test app and try to authenticate; Fingerprint authentication should work normally Bug: 277854521 (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:c35a712ab4308e0281c8b2a7adfae8de42d28a6a) Change-Id: I79e389ad830d82326f0badb7382744faede17a5c --- .../biometrics/BiometricSensorPrivacy.java | 25 ++++ .../BiometricSensorPrivacyImpl.java | 37 +++++ .../server/biometrics/BiometricService.java | 17 ++- .../server/biometrics/PreAuthInfo.java | 37 +++-- .../server/biometrics/AuthSessionTest.java | 4 +- .../server/biometrics/PreAuthInfoTest.java | 139 ++++++++++++++++++ 6 files changed, 233 insertions(+), 26 deletions(-) create mode 100644 services/core/java/com/android/server/biometrics/BiometricSensorPrivacy.java create mode 100644 services/core/java/com/android/server/biometrics/BiometricSensorPrivacyImpl.java create mode 100644 services/tests/servicestests/src/com/android/server/biometrics/PreAuthInfoTest.java diff --git a/services/core/java/com/android/server/biometrics/BiometricSensorPrivacy.java b/services/core/java/com/android/server/biometrics/BiometricSensorPrivacy.java new file mode 100644 index 0000000000000..6727fbcdec664 --- /dev/null +++ b/services/core/java/com/android/server/biometrics/BiometricSensorPrivacy.java @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2023 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; + +/** + * Interface for biometric operations to get camera privacy state. + */ +public interface BiometricSensorPrivacy { + /* Returns true if privacy is enabled and camera access is disabled. */ + boolean isCameraPrivacyEnabled(); +} diff --git a/services/core/java/com/android/server/biometrics/BiometricSensorPrivacyImpl.java b/services/core/java/com/android/server/biometrics/BiometricSensorPrivacyImpl.java new file mode 100644 index 0000000000000..b6701da1d3487 --- /dev/null +++ b/services/core/java/com/android/server/biometrics/BiometricSensorPrivacyImpl.java @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2023 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.SensorPrivacyManager.Sensors.CAMERA; + +import android.annotation.Nullable; +import android.hardware.SensorPrivacyManager; + +public class BiometricSensorPrivacyImpl implements + BiometricSensorPrivacy { + private final SensorPrivacyManager mSensorPrivacyManager; + + public BiometricSensorPrivacyImpl(@Nullable SensorPrivacyManager sensorPrivacyManager) { + mSensorPrivacyManager = sensorPrivacyManager; + } + + @Override + public boolean isCameraPrivacyEnabled() { + return mSensorPrivacyManager != null && mSensorPrivacyManager + .isSensorPrivacyEnabled(SensorPrivacyManager.TOGGLE_TYPE_SOFTWARE, CAMERA); + } +} diff --git a/services/core/java/com/android/server/biometrics/BiometricService.java b/services/core/java/com/android/server/biometrics/BiometricService.java index 0942d85275652..1fa97a3cb97f6 100644 --- a/services/core/java/com/android/server/biometrics/BiometricService.java +++ b/services/core/java/com/android/server/biometrics/BiometricService.java @@ -33,6 +33,7 @@ import android.content.Context; import android.content.pm.PackageManager; import android.content.pm.UserInfo; import android.database.ContentObserver; +import android.hardware.SensorPrivacyManager; import android.hardware.biometrics.BiometricAuthenticator; import android.hardware.biometrics.BiometricConstants; import android.hardware.biometrics.BiometricPrompt; @@ -124,6 +125,8 @@ public class BiometricService extends SystemService { AuthSession mAuthSession; private final Handler mHandler = new Handler(Looper.getMainLooper()); + private final BiometricSensorPrivacy mBiometricSensorPrivacy; + /** * Tracks authenticatorId invalidation. For more details, see * {@link com.android.server.biometrics.sensors.InvalidationRequesterClient}. @@ -933,7 +936,7 @@ public class BiometricService extends SystemService { return PreAuthInfo.create(mTrustManager, mDevicePolicyManager, mSettingObserver, mSensors, userId, promptInfo, opPackageName, false /* checkDevicePolicyManager */, - getContext()); + getContext(), mBiometricSensorPrivacy); } /** @@ -1026,6 +1029,11 @@ public class BiometricService extends SystemService { public UserManager getUserManager(Context context) { return context.getSystemService(UserManager.class); } + + public BiometricSensorPrivacy getBiometricSensorPrivacy(Context context) { + return new BiometricSensorPrivacyImpl(context.getSystemService( + SensorPrivacyManager.class)); + } } /** @@ -1054,6 +1062,7 @@ public class BiometricService extends SystemService { mRequestCounter = mInjector.getRequestGenerator(); mBiometricContext = injector.getBiometricContext(context); mUserManager = injector.getUserManager(context); + mBiometricSensorPrivacy = injector.getBiometricSensorPrivacy(context); try { injector.getActivityManagerService().registerUserSwitchObserver( @@ -1290,7 +1299,7 @@ public class BiometricService extends SystemService { final PreAuthInfo preAuthInfo = PreAuthInfo.create(mTrustManager, mDevicePolicyManager, mSettingObserver, mSensors, userId, promptInfo, opPackageName, promptInfo.isDisallowBiometricsIfPolicyExists(), - getContext()); + getContext(), mBiometricSensorPrivacy); final Pair preAuthStatus = preAuthInfo.getPreAuthenticateStatus(); @@ -1300,9 +1309,7 @@ public class BiometricService extends SystemService { + promptInfo.isIgnoreEnrollmentState()); // BIOMETRIC_ERROR_SENSOR_PRIVACY_ENABLED is added so that BiometricPrompt can // be shown for this case. - if (preAuthStatus.second == BiometricConstants.BIOMETRIC_SUCCESS - || preAuthStatus.second - == BiometricConstants.BIOMETRIC_ERROR_SENSOR_PRIVACY_ENABLED) { + if (preAuthStatus.second == BiometricConstants.BIOMETRIC_SUCCESS) { // If BIOMETRIC_WEAK or BIOMETRIC_STRONG are allowed, but not enrolled, but // CREDENTIAL is requested and available, set the bundle to only request // CREDENTIAL. diff --git a/services/core/java/com/android/server/biometrics/PreAuthInfo.java b/services/core/java/com/android/server/biometrics/PreAuthInfo.java index 3813fd1971a66..44e3d1ede19c2 100644 --- a/services/core/java/com/android/server/biometrics/PreAuthInfo.java +++ b/services/core/java/com/android/server/biometrics/PreAuthInfo.java @@ -27,7 +27,6 @@ import android.annotation.NonNull; import android.app.admin.DevicePolicyManager; import android.app.trust.ITrustManager; import android.content.Context; -import android.hardware.SensorPrivacyManager; import android.hardware.biometrics.BiometricAuthenticator; import android.hardware.biometrics.BiometricManager; import android.hardware.biometrics.PromptInfo; @@ -73,13 +72,16 @@ class PreAuthInfo { final Context context; private final boolean mBiometricRequested; private final int mBiometricStrengthRequested; + private final BiometricSensorPrivacy mBiometricSensorPrivacy; + private PreAuthInfo(boolean biometricRequested, int biometricStrengthRequested, boolean credentialRequested, List eligibleSensors, List> ineligibleSensors, boolean credentialAvailable, boolean confirmationRequested, boolean ignoreEnrollmentState, int userId, - Context context) { + Context context, BiometricSensorPrivacy biometricSensorPrivacy) { mBiometricRequested = biometricRequested; mBiometricStrengthRequested = biometricStrengthRequested; + mBiometricSensorPrivacy = biometricSensorPrivacy; this.credentialRequested = credentialRequested; this.eligibleSensors = eligibleSensors; @@ -96,7 +98,8 @@ class PreAuthInfo { BiometricService.SettingObserver settingObserver, List sensors, int userId, PromptInfo promptInfo, String opPackageName, - boolean checkDevicePolicyManager, Context context) + boolean checkDevicePolicyManager, Context context, + BiometricSensorPrivacy biometricSensorPrivacy) throws RemoteException { final boolean confirmationRequested = promptInfo.isConfirmationRequested(); @@ -124,7 +127,7 @@ class PreAuthInfo { checkDevicePolicyManager, requestedStrength, promptInfo.getAllowedSensorIds(), promptInfo.isIgnoreEnrollmentState(), - context); + biometricSensorPrivacy); Slog.d(TAG, "Package: " + opPackageName + " Sensor ID: " + sensor.id @@ -138,7 +141,7 @@ class PreAuthInfo { // // Note: if only a certain sensor is required and the privacy is enabled, // canAuthenticate() will return false. - if (status == AUTHENTICATOR_OK || status == BIOMETRIC_SENSOR_PRIVACY_ENABLED) { + if (status == AUTHENTICATOR_OK) { eligibleSensors.add(sensor); } else { ineligibleSensors.add(new Pair<>(sensor, status)); @@ -148,7 +151,7 @@ class PreAuthInfo { return new PreAuthInfo(biometricRequested, requestedStrength, credentialRequested, eligibleSensors, ineligibleSensors, credentialAvailable, confirmationRequested, - promptInfo.isIgnoreEnrollmentState(), userId, context); + promptInfo.isIgnoreEnrollmentState(), userId, context, biometricSensorPrivacy); } /** @@ -165,7 +168,7 @@ class PreAuthInfo { BiometricSensor sensor, int userId, String opPackageName, boolean checkDevicePolicyManager, int requestedStrength, @NonNull List requestedSensorIds, - boolean ignoreEnrollmentState, Context context) { + boolean ignoreEnrollmentState, BiometricSensorPrivacy biometricSensorPrivacy) { if (!requestedSensorIds.isEmpty() && !requestedSensorIds.contains(sensor.id)) { return BIOMETRIC_NO_HARDWARE; @@ -191,12 +194,10 @@ class PreAuthInfo { && !ignoreEnrollmentState) { return BIOMETRIC_NOT_ENROLLED; } - final SensorPrivacyManager sensorPrivacyManager = context - .getSystemService(SensorPrivacyManager.class); - if (sensorPrivacyManager != null && sensor.modality == TYPE_FACE) { - if (sensorPrivacyManager - .isSensorPrivacyEnabled(SensorPrivacyManager.Sensors.CAMERA, userId)) { + if (biometricSensorPrivacy != null && sensor.modality == TYPE_FACE) { + if (biometricSensorPrivacy.isCameraPrivacyEnabled()) { + //Camera privacy is enabled as the access is disabled return BIOMETRIC_SENSOR_PRIVACY_ENABLED; } } @@ -292,13 +293,9 @@ class PreAuthInfo { @AuthenticatorStatus final int status; @BiometricAuthenticator.Modality int modality = TYPE_NONE; - final SensorPrivacyManager sensorPrivacyManager = context - .getSystemService(SensorPrivacyManager.class); - boolean cameraPrivacyEnabled = false; - if (sensorPrivacyManager != null) { - cameraPrivacyEnabled = sensorPrivacyManager - .isSensorPrivacyEnabled(SensorPrivacyManager.Sensors.CAMERA, userId); + if (mBiometricSensorPrivacy != null) { + cameraPrivacyEnabled = mBiometricSensorPrivacy.isCameraPrivacyEnabled(); } if (mBiometricRequested && credentialRequested) { @@ -315,7 +312,7 @@ class PreAuthInfo { // and the face sensor privacy is enabled then return // BIOMETRIC_SENSOR_PRIVACY_ENABLED. // - // Note: This sensor will still be eligible for calls to authenticate. + // Note: This sensor will not be eligible for calls to authenticate. status = BIOMETRIC_SENSOR_PRIVACY_ENABLED; } else { status = AUTHENTICATOR_OK; @@ -340,7 +337,7 @@ class PreAuthInfo { // If the only modality requested is face and the privacy is enabled // then return BIOMETRIC_SENSOR_PRIVACY_ENABLED. // - // Note: This sensor will still be eligible for calls to authenticate. + // Note: This sensor will not be eligible for calls to authenticate. status = BIOMETRIC_SENSOR_PRIVACY_ENABLED; } else { status = AUTHENTICATOR_OK; diff --git a/services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java b/services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java index 8912591ecacd2..8346050c3c895 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java @@ -106,6 +106,7 @@ public class AuthSessionTest { @Mock private KeyStore mKeyStore; @Mock private AuthSession.ClientDeathReceiver mClientDeathReceiver; @Mock private BiometricFrameworkStatsLogger mBiometricFrameworkStatsLogger; + @Mock BiometricSensorPrivacy mBiometricSensorPrivacy; private Random mRandom; private IBinder mToken; @@ -607,7 +608,8 @@ public class AuthSessionTest { promptInfo, TEST_PACKAGE, checkDevicePolicyManager, - mContext); + mContext, + mBiometricSensorPrivacy); } private AuthSession createAuthSession(List sensors, diff --git a/services/tests/servicestests/src/com/android/server/biometrics/PreAuthInfoTest.java b/services/tests/servicestests/src/com/android/server/biometrics/PreAuthInfoTest.java new file mode 100644 index 0000000000000..0c98c8d88d837 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/biometrics/PreAuthInfoTest.java @@ -0,0 +1,139 @@ +/* + * Copyright (C) 2023 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.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_FEATURES_NONE; +import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FACE; + +import static com.android.server.biometrics.sensors.LockoutTracker.LOCKOUT_NONE; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.when; + +import android.app.admin.DevicePolicyManager; +import android.app.trust.ITrustManager; +import android.content.Context; +import android.hardware.biometrics.BiometricManager; +import android.hardware.biometrics.IBiometricAuthenticator; +import android.hardware.biometrics.PromptInfo; +import android.os.RemoteException; +import android.platform.test.annotations.Presubmit; + +import androidx.test.filters.SmallTest; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; + +import java.util.List; + +@Presubmit +@SmallTest +public class PreAuthInfoTest { + @Rule + public final MockitoRule mMockitoRule = MockitoJUnit.rule(); + + private static final int SENSOR_ID_FACE = 1; + private static final String TEST_PACKAGE_NAME = "PreAuthInfoTestPackage"; + + @Mock + IBiometricAuthenticator mFaceAuthenticator; + @Mock + Context mContext; + @Mock + ITrustManager mTrustManager; + @Mock + DevicePolicyManager mDevicePolicyManager; + @Mock + BiometricService.SettingObserver mSettingObserver; + @Mock + BiometricSensorPrivacy mBiometricSensorPrivacyUtil; + + @Before + public void setup() throws RemoteException { + when(mTrustManager.isDeviceSecure(anyInt(), anyInt())).thenReturn(true); + when(mDevicePolicyManager.getKeyguardDisabledFeatures(any(), anyInt())) + .thenReturn(KEYGUARD_DISABLE_FEATURES_NONE); + when(mSettingObserver.getEnabledForApps(anyInt())).thenReturn(true); + when(mFaceAuthenticator.hasEnrolledTemplates(anyInt(), any())).thenReturn(true); + when(mFaceAuthenticator.isHardwareDetected(any())).thenReturn(true); + when(mFaceAuthenticator.getLockoutModeForUser(anyInt())) + .thenReturn(LOCKOUT_NONE); + } + + @Test + public void testFaceAuthentication_whenCameraPrivacyIsEnabled() throws Exception { + when(mBiometricSensorPrivacyUtil.isCameraPrivacyEnabled()).thenReturn(true); + + BiometricSensor sensor = new BiometricSensor(mContext, SENSOR_ID_FACE, TYPE_FACE, + BiometricManager.Authenticators.BIOMETRIC_STRONG, mFaceAuthenticator) { + @Override + boolean confirmationAlwaysRequired(int userId) { + return false; + } + + @Override + boolean confirmationSupported() { + return false; + } + }; + PromptInfo promptInfo = new PromptInfo(); + promptInfo.setConfirmationRequested(false /* requireConfirmation */); + promptInfo.setAuthenticators(BiometricManager.Authenticators.BIOMETRIC_STRONG); + promptInfo.setDisallowBiometricsIfPolicyExists(false /* checkDevicePolicy */); + PreAuthInfo preAuthInfo = PreAuthInfo.create(mTrustManager, mDevicePolicyManager, + mSettingObserver, List.of(sensor), + 0 /* userId */, promptInfo, TEST_PACKAGE_NAME, + false /* checkDevicePolicyManager */, mContext, mBiometricSensorPrivacyUtil); + + assertThat(preAuthInfo.eligibleSensors).isEmpty(); + } + + @Test + public void testFaceAuthentication_whenCameraPrivacyIsDisabled() throws Exception { + when(mBiometricSensorPrivacyUtil.isCameraPrivacyEnabled()).thenReturn(false); + + BiometricSensor sensor = new BiometricSensor(mContext, SENSOR_ID_FACE, TYPE_FACE, + BiometricManager.Authenticators.BIOMETRIC_STRONG, mFaceAuthenticator) { + @Override + boolean confirmationAlwaysRequired(int userId) { + return false; + } + + @Override + boolean confirmationSupported() { + return false; + } + }; + PromptInfo promptInfo = new PromptInfo(); + promptInfo.setConfirmationRequested(false /* requireConfirmation */); + promptInfo.setAuthenticators(BiometricManager.Authenticators.BIOMETRIC_STRONG); + promptInfo.setDisallowBiometricsIfPolicyExists(false /* checkDevicePolicy */); + PreAuthInfo preAuthInfo = PreAuthInfo.create(mTrustManager, mDevicePolicyManager, + mSettingObserver, List.of(sensor), + 0 /* userId */, promptInfo, TEST_PACKAGE_NAME, + false /* checkDevicePolicyManager */, mContext, mBiometricSensorPrivacyUtil); + + assertThat(preAuthInfo.eligibleSensors).hasSize(1); + } +}