| /* |
| * Copyright (C) 2020 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.BiometricAuthenticator.TYPE_FACE; |
| import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT; |
| |
| import static junit.framework.Assert.assertEquals; |
| import static junit.framework.Assert.assertFalse; |
| import static junit.framework.Assert.assertTrue; |
| |
| import static org.mockito.ArgumentMatchers.any; |
| import static org.mockito.ArgumentMatchers.anyInt; |
| import static org.mockito.ArgumentMatchers.eq; |
| import static org.mockito.Mockito.mock; |
| import static org.mockito.Mockito.verify; |
| import static org.mockito.Mockito.when; |
| |
| import android.app.admin.DevicePolicyManager; |
| import android.app.trust.ITrustManager; |
| import android.hardware.biometrics.BiometricManager.Authenticators; |
| import android.hardware.biometrics.BiometricPrompt; |
| import android.hardware.biometrics.IBiometricAuthenticator; |
| import android.hardware.biometrics.IBiometricSensorReceiver; |
| import android.hardware.biometrics.IBiometricServiceReceiver; |
| import android.hardware.biometrics.IBiometricSysuiReceiver; |
| import android.os.Binder; |
| import android.os.Bundle; |
| import android.os.IBinder; |
| import android.os.RemoteException; |
| import android.platform.test.annotations.Presubmit; |
| import android.security.KeyStore; |
| |
| import androidx.test.filters.SmallTest; |
| |
| import com.android.internal.statusbar.IStatusBarService; |
| |
| import org.junit.Before; |
| import org.junit.Test; |
| import org.mockito.Mock; |
| import org.mockito.MockitoAnnotations; |
| |
| import java.util.ArrayList; |
| import java.util.List; |
| import java.util.Random; |
| |
| @Presubmit |
| @SmallTest |
| public class AuthSessionTest { |
| |
| private static final String TEST_PACKAGE = "test_package"; |
| |
| @Mock private ITrustManager mTrustManager; |
| @Mock private DevicePolicyManager mDevicePolicyManager; |
| @Mock private BiometricService.SettingObserver mSettingObserver; |
| @Mock private IBiometricSensorReceiver mSensorReceiver; |
| @Mock private IBiometricServiceReceiver mClientReceiver; |
| @Mock private IStatusBarService mStatusBarService; |
| @Mock private IBiometricSysuiReceiver mSysuiReceiver; |
| @Mock private KeyStore mKeyStore; |
| |
| private Random mRandom; |
| private IBinder mToken; |
| |
| // Assume all tests can be done with the same set of sensors for now. |
| private List<BiometricSensor> mSensors; |
| |
| @Before |
| public void setUp() throws Exception { |
| MockitoAnnotations.initMocks(this); |
| mRandom = new Random(); |
| mToken = new Binder(); |
| mSensors = new ArrayList<>(); |
| } |
| |
| @Test |
| public void testNewAuthSession_eligibleSensorsSetToStateUnknown() throws RemoteException { |
| setupFingerprint(0 /* id */); |
| setupFace(1 /* id */, false /* confirmationAlwaysRequired */); |
| |
| final AuthSession session = createAuthSession(mSensors, |
| false /* checkDevicePolicyManager */, |
| Authenticators.BIOMETRIC_STRONG, |
| 0 /* operationId */, |
| 0 /* userId */, |
| 0 /* callingUid */, |
| 0 /* callingPid */, |
| 0 /* callingUserId */); |
| |
| for (BiometricSensor sensor : session.mPreAuthInfo.eligibleSensors) { |
| assertEquals(BiometricSensor.STATE_UNKNOWN, sensor.getSensorState()); |
| } |
| } |
| |
| @Test |
| public void testStartNewAuthSession() |
| throws RemoteException { |
| setupFace(0 /* id */, false /* confirmationAlwaysRequired */); |
| setupFingerprint(1 /* id */); |
| |
| final boolean requireConfirmation = true; |
| final long operationId = 123; |
| final int userId = 10; |
| final int callingUid = 100; |
| final int callingPid = 1000; |
| final int callingUserId = 10000; |
| |
| final AuthSession session = createAuthSession(mSensors, |
| false /* checkDevicePolicyManager */, |
| Authenticators.BIOMETRIC_STRONG, |
| operationId, |
| userId, |
| callingUid, |
| callingPid, |
| callingUserId); |
| assertEquals(mSensors.size(), session.mPreAuthInfo.eligibleSensors.size()); |
| |
| for (BiometricSensor sensor : session.mPreAuthInfo.eligibleSensors) { |
| assertEquals(BiometricSensor.STATE_UNKNOWN, sensor.getSensorState()); |
| assertEquals(0, sensor.getCookie()); |
| } |
| |
| session.goToInitialState(); |
| for (BiometricSensor sensor : session.mPreAuthInfo.eligibleSensors) { |
| assertEquals(BiometricSensor.STATE_WAITING_FOR_COOKIE, sensor.getSensorState()); |
| assertTrue("Cookie must be >0", sensor.getCookie() > 0); |
| verify(sensor.impl).prepareForAuthentication( |
| eq(sensor.confirmationSupported() && requireConfirmation), |
| eq(mToken), |
| eq(operationId), |
| eq(userId), |
| eq(mSensorReceiver), |
| eq(TEST_PACKAGE), |
| eq(sensor.getCookie()), |
| eq(callingUid), |
| eq(callingPid), |
| eq(callingUserId)); |
| } |
| |
| final int cookie1 = session.mPreAuthInfo.eligibleSensors.get(0).getCookie(); |
| // TODO: RequireConfirmation being removed from this interface soon. True for face. |
| session.onCookieReceived(cookie1, true /* requireConfirmation */); |
| for (BiometricSensor sensor : session.mPreAuthInfo.eligibleSensors) { |
| if (cookie1 == sensor.getCookie()) { |
| assertEquals(BiometricSensor.STATE_COOKIE_RETURNED, sensor.getSensorState()); |
| } else { |
| assertEquals(BiometricSensor.STATE_WAITING_FOR_COOKIE, sensor.getSensorState()); |
| } |
| } |
| assertFalse(session.allCookiesReceived()); |
| |
| final int cookie2 = session.mPreAuthInfo.eligibleSensors.get(1).getCookie(); |
| // TODO: RequireConfirmation being removed from this interface soon. False for fingerprint. |
| session.onCookieReceived(cookie2, false /* requireConfirmation */); |
| assertTrue(session.allCookiesReceived()); |
| |
| for (BiometricSensor sensor : session.mPreAuthInfo.eligibleSensors) { |
| verify(sensor.impl).startPreparedClient(eq(sensor.getCookie())); |
| assertEquals(BiometricSensor.STATE_AUTHENTICATING, sensor.getSensorState()); |
| } |
| } |
| |
| private PreAuthInfo createPreAuthInfo(List<BiometricSensor> sensors, int userId, Bundle bundle, |
| boolean checkDevicePolicyManager) throws RemoteException { |
| return PreAuthInfo.create(mTrustManager, |
| mDevicePolicyManager, |
| mSettingObserver, |
| sensors, |
| userId, |
| bundle, |
| TEST_PACKAGE, |
| checkDevicePolicyManager); |
| } |
| |
| private AuthSession createAuthSession(List<BiometricSensor> sensors, |
| boolean checkDevicePolicyManager, @Authenticators.Types int authenticators, |
| long operationId, int userId, |
| int callingUid, int callingPid, int callingUserId) |
| throws RemoteException { |
| |
| final Bundle bundle = createBiometricPromptBundle(authenticators); |
| |
| final PreAuthInfo preAuthInfo = createPreAuthInfo(sensors, userId, bundle, |
| checkDevicePolicyManager); |
| |
| return new AuthSession(mStatusBarService, mSysuiReceiver, mKeyStore, |
| mRandom, preAuthInfo, mToken, operationId, userId, mSensorReceiver, |
| mClientReceiver, TEST_PACKAGE, bundle, callingUid, |
| callingPid, callingUserId); |
| } |
| |
| private Bundle createBiometricPromptBundle(@Authenticators.Types int authenticators) { |
| Bundle bundle = new Bundle(); |
| bundle.putInt(BiometricPrompt.KEY_AUTHENTICATORS_ALLOWED, authenticators); |
| return bundle; |
| } |
| |
| |
| private void setupFingerprint(int id) throws RemoteException { |
| IBiometricAuthenticator fingerprintAuthenticator = mock(IBiometricAuthenticator.class); |
| when(fingerprintAuthenticator.isHardwareDetected(any())).thenReturn(true); |
| when(fingerprintAuthenticator.hasEnrolledTemplates(anyInt(), any())).thenReturn(true); |
| mSensors.add(new BiometricSensor(id, |
| TYPE_FINGERPRINT /* modality */, |
| Authenticators.BIOMETRIC_STRONG /* strength */, |
| fingerprintAuthenticator) { |
| @Override |
| boolean confirmationAlwaysRequired(int userId) { |
| return false; // no-op / unsupported |
| } |
| |
| @Override |
| boolean confirmationSupported() { |
| return false; // fingerprint does not support confirmation |
| } |
| }); |
| } |
| |
| private void setupFace(int id, boolean confirmationAlwaysRequired) throws RemoteException { |
| IBiometricAuthenticator faceAuthenticator = mock(IBiometricAuthenticator.class); |
| when(faceAuthenticator.isHardwareDetected(any())).thenReturn(true); |
| when(faceAuthenticator.hasEnrolledTemplates(anyInt(), any())).thenReturn(true); |
| mSensors.add(new BiometricSensor(id, |
| TYPE_FACE /* modality */, |
| Authenticators.BIOMETRIC_STRONG /* strength */, |
| faceAuthenticator) { |
| @Override |
| boolean confirmationAlwaysRequired(int userId) { |
| return confirmationAlwaysRequired; |
| } |
| |
| @Override |
| boolean confirmationSupported() { |
| return true; |
| } |
| }); |
| |
| when(mSettingObserver.getFaceEnabledForApps(anyInt())).thenReturn(true); |
| } |
| } |