Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 98e9225f authored by Kevin Chyn's avatar Kevin Chyn
Browse files

Implementation for BIOMETRIC_ERROR_SECURITY_UPDATE_REQUIRED

Bug: 148419762
Test: atest BiometricServiceTest
Test: BiometricPromptDemo

Change-Id: I2d3ced391e43858ac48963718c832ed84d4e7d0b
parent eeadeaac
Loading
Loading
Loading
Loading
+130 −74
Original line number Diff line number Diff line
@@ -25,6 +25,7 @@ import static android.hardware.biometrics.BiometricAuthenticator.TYPE_IRIS;
import static android.hardware.biometrics.BiometricAuthenticator.TYPE_NONE;
import static android.hardware.biometrics.BiometricManager.Authenticators;

import android.annotation.IntDef;
import android.app.ActivityManager;
import android.app.IActivityManager;
import android.app.UserSwitchObserver;
@@ -83,6 +84,25 @@ public class BiometricService extends SystemService {
    static final String TAG = "BiometricService";
    private static final boolean DEBUG = true;

    private static final int BIOMETRIC_NO_HARDWARE = 0;
    private static final int BIOMETRIC_OK = 1;
    private static final int BIOMETRIC_DISABLED_BY_DEVICE_POLICY = 2;
    private static final int BIOMETRIC_INSUFFICIENT_STRENGTH = 3;
    private static final int BIOMETRIC_INSUFFICIENT_STRENGTH_AFTER_DOWNGRADE = 4;
    private static final int BIOMETRIC_HARDWARE_NOT_DETECTED = 5;
    private static final int BIOMETRIC_NOT_ENROLLED = 6;
    private static final int BIOMETRIC_NOT_ENABLED_FOR_APPS = 7;

    @IntDef({BIOMETRIC_NO_HARDWARE,
            BIOMETRIC_OK,
            BIOMETRIC_DISABLED_BY_DEVICE_POLICY,
            BIOMETRIC_INSUFFICIENT_STRENGTH,
            BIOMETRIC_INSUFFICIENT_STRENGTH_AFTER_DOWNGRADE,
            BIOMETRIC_HARDWARE_NOT_DETECTED,
            BIOMETRIC_NOT_ENROLLED,
            BIOMETRIC_NOT_ENABLED_FOR_APPS})
    @interface BiometricStatus {}

    private static final int MSG_ON_AUTHENTICATION_SUCCEEDED = 2;
    private static final int MSG_ON_AUTHENTICATION_REJECTED = 3;
    private static final int MSG_ON_ERROR = 4;
@@ -206,7 +226,7 @@ public class BiometricService extends SystemService {
        }

        boolean isAllowDeviceCredential() {
            return Utils.isDeviceCredentialAllowed(mBundle);
            return Utils.isCredentialRequested(mBundle);
        }
    }

@@ -372,16 +392,20 @@ public class BiometricService extends SystemService {
         * strength.
         * @return a bitfield, see {@link Authenticators}
         */
        public int getActualStrength() {
        int getActualStrength() {
            return OEMStrength | updatedStrength;
        }

        boolean isDowngraded() {
            return OEMStrength != updatedStrength;
        }

        /**
         * Stores the updated strength, which takes effect whenever {@link #getActualStrength()}
         * is checked.
         * @param newStrength
         */
        public void updateStrength(int newStrength) {
        void updateStrength(int newStrength) {
            String log = "updateStrength: Before(" + toString() + ")";
            updatedStrength = newStrength;
            log += " After(" + toString() + ")";
@@ -1007,6 +1031,79 @@ public class BiometricService extends SystemService {
        return isBiometricDisabled;
    }

    private static int biometricStatusToBiometricConstant(@BiometricStatus int status) {
        switch (status) {
            case BIOMETRIC_NO_HARDWARE:
                return BiometricConstants.BIOMETRIC_ERROR_HW_NOT_PRESENT;
            case BIOMETRIC_OK:
                return BiometricConstants.BIOMETRIC_SUCCESS;
            case BIOMETRIC_DISABLED_BY_DEVICE_POLICY:
                return BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE;
            case BIOMETRIC_INSUFFICIENT_STRENGTH:
                return BiometricConstants.BIOMETRIC_ERROR_HW_NOT_PRESENT;
            case BIOMETRIC_INSUFFICIENT_STRENGTH_AFTER_DOWNGRADE:
                return BiometricConstants.BIOMETRIC_ERROR_SECURITY_UPDATE_REQUIRED;
            case BIOMETRIC_HARDWARE_NOT_DETECTED:
                return BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE;
            case BIOMETRIC_NOT_ENROLLED:
                return BiometricConstants.BIOMETRIC_ERROR_NO_BIOMETRICS;
            case BIOMETRIC_NOT_ENABLED_FOR_APPS:
                return BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE;
            default:
                return BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE;
        }
    }

    /**
     * Returns the status of the authenticator, with errors returned in a specific priority order.
     * For example, {@link #BIOMETRIC_INSUFFICIENT_STRENGTH_AFTER_DOWNGRADE} is only returned
     * if it has enrollments, and is enabled for apps.
     *
     * We should only return the modality if the authenticator should be exposed. e.g.
     * BIOMETRIC_NOT_ENROLLED_FOR_APPS should not expose the authenticator's type.
     *
     * @return A Pair with `first` being modality, and `second` being @BiometricStatus
     */
    private Pair<Integer, Integer> getStatusForBiometricAuthenticator(
            AuthenticatorWrapper authenticator, int userId, String opPackageName,
            boolean checkDevicePolicyManager, int requestedStrength) {
        if (checkDevicePolicyManager) {
            if (isBiometricDisabledByDevicePolicy(authenticator.modality, userId)) {
                return new Pair<>(TYPE_NONE, BIOMETRIC_DISABLED_BY_DEVICE_POLICY);
            }
        }

        final boolean wasStrongEnough =
                Utils.isAtLeastStrength(authenticator.OEMStrength, requestedStrength);
        final boolean isStrongEnough =
                Utils.isAtLeastStrength(authenticator.getActualStrength(), requestedStrength);

        if (wasStrongEnough && !isStrongEnough) {
            return new Pair<>(authenticator.modality,
                    BIOMETRIC_INSUFFICIENT_STRENGTH_AFTER_DOWNGRADE);
        } else if (!wasStrongEnough) {
            return new Pair<>(TYPE_NONE, BIOMETRIC_INSUFFICIENT_STRENGTH);
        }

        try {
            if (!authenticator.impl.isHardwareDetected(opPackageName)) {
                return new Pair<>(authenticator.modality, BIOMETRIC_HARDWARE_NOT_DETECTED);
            }

            if (!authenticator.impl.hasEnrolledTemplates(userId, opPackageName)) {
                return new Pair<>(authenticator.modality, BIOMETRIC_NOT_ENROLLED);
            }
        } catch (RemoteException e) {
            return new Pair<>(authenticator.modality, BIOMETRIC_HARDWARE_NOT_DETECTED);
        }

        if (!isEnabledForApp(authenticator.modality, userId)) {
            return new Pair<>(TYPE_NONE, BIOMETRIC_NOT_ENABLED_FOR_APPS);
        }

        return new Pair<>(authenticator.modality, BIOMETRIC_OK);
    }

    /**
     * Depending on the requested authentication (credential/biometric combination), checks their
     * availability.
@@ -1029,10 +1126,9 @@ public class BiometricService extends SystemService {
    private Pair<Integer, Integer> checkAndGetAuthenticators(int userId, Bundle bundle,
            String opPackageName, boolean checkDevicePolicyManager) throws RemoteException {

        final boolean biometricRequested = Utils.isBiometricAllowed(bundle);
        final boolean credentialRequested = Utils.isDeviceCredentialAllowed(bundle);
        final boolean biometricRequested = Utils.isBiometricRequested(bundle);
        final boolean credentialRequested = Utils.isCredentialRequested(bundle);

        final boolean biometricOk;
        final boolean credentialOk = mTrustManager.isDeviceSecure(userId);

        // Assuming that biometric authenticators are listed in priority-order, the rest of this
@@ -1041,96 +1137,56 @@ public class BiometricService extends SystemService {
        // the correct error. Error strings that are modality-specific should also respect the
        // priority-order.

        // Find first biometric authenticator that's strong enough, detected, enrolled, and enabled.
        boolean disabledByDevicePolicy = false;
        boolean hasSufficientStrength = false;
        boolean isHardwareDetected = false;
        boolean hasTemplatesEnrolled = false;
        boolean enabledForApps = false;
        int firstBiometricModality = TYPE_NONE;
        @BiometricStatus int firstBiometricStatus = BIOMETRIC_NO_HARDWARE;

        int biometricModality = TYPE_NONE;
        @BiometricStatus int biometricStatus = BIOMETRIC_NO_HARDWARE;

        int modality = TYPE_NONE;
        int firstHwAvailable = TYPE_NONE;
        for (AuthenticatorWrapper authenticator : mAuthenticators) {
            final int actualStrength = authenticator.getActualStrength();
            final int requestedStrength = Utils.getPublicBiometricStrength(bundle);
            Pair<Integer, Integer> result = getStatusForBiometricAuthenticator(
                    authenticator, userId, opPackageName, checkDevicePolicyManager,
                    requestedStrength);

            if (isBiometricDisabledByDevicePolicy(authenticator.modality, userId)) {
                disabledByDevicePolicy = true;
                continue;
            }
            disabledByDevicePolicy = false;

            if (!Utils.isAtLeastStrength(actualStrength, requestedStrength)) {
                continue;
            }
            hasSufficientStrength = true;

            if (!authenticator.impl.isHardwareDetected(opPackageName)) {
                continue;
            }
            isHardwareDetected = true;
            biometricStatus = result.second;

            if (firstHwAvailable == TYPE_NONE) {
                // Store the first one since we want to return the error in correct
                // priority order.
                firstHwAvailable = authenticator.modality;
            }
            Slog.d(TAG, "Authenticator ID: " + authenticator.id
                    + " Modality: " + authenticator.modality
                    + " ReportedModality: " + result.first
                    + " Status: " + biometricStatus);

            if (!authenticator.impl.hasEnrolledTemplates(userId, opPackageName)) {
                continue;
            if (firstBiometricModality == TYPE_NONE) {
                firstBiometricModality = result.first;
                firstBiometricStatus = biometricStatus;
            }
            hasTemplatesEnrolled = true;

            if (!isEnabledForApp(authenticator.modality, userId)) {
                continue;
            }
            enabledForApps = true;
            modality = authenticator.modality;
            if (biometricStatus == BIOMETRIC_OK) {
                biometricModality = result.first;
                break;
            }

        biometricOk = !disabledByDevicePolicy
                && hasSufficientStrength && isHardwareDetected
                && hasTemplatesEnrolled && enabledForApps;

        Slog.d(TAG, "checkAndGetAuthenticators: user=" + userId
                + " checkDevicePolicyManager=" + checkDevicePolicyManager
                + " isHardwareDetected=" + isHardwareDetected
                + " hasTemplatesEnrolled=" + hasTemplatesEnrolled
                + " enabledForApps=" + enabledForApps
                + " disabledByDevicePolicy=" + disabledByDevicePolicy);
        }

        if (biometricRequested && credentialRequested) {
            if (credentialOk || biometricOk) {
                if (!biometricOk) {
            if (credentialOk || biometricStatus == BIOMETRIC_OK) {
                if (biometricStatus != BIOMETRIC_OK) {
                    // If there's a problem with biometrics but device credential is
                    // allowed, only show credential UI.
                    bundle.putInt(BiometricPrompt.KEY_AUTHENTICATORS_ALLOWED,
                            Authenticators.DEVICE_CREDENTIAL);
                }
                return new Pair<>(modality, BiometricConstants.BIOMETRIC_SUCCESS);
                return new Pair<>(biometricModality, BiometricConstants.BIOMETRIC_SUCCESS);
            } else {
                return new Pair<>(firstHwAvailable,
                return new Pair<>(firstBiometricModality,
                        BiometricConstants.BIOMETRIC_ERROR_NO_BIOMETRICS);
            }
        } else if (biometricRequested) {
            if (biometricOk) {
                return new Pair<>(modality, BiometricConstants.BIOMETRIC_SUCCESS);
            } else if (disabledByDevicePolicy) {
                return new Pair<>(TYPE_NONE, BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE);
            } else if (!hasSufficientStrength) {
                return new Pair<>(TYPE_NONE, BiometricConstants.BIOMETRIC_ERROR_HW_NOT_PRESENT);
            } else if (!isHardwareDetected) {
                return new Pair<>(firstHwAvailable,
                        BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE);
            } else if (!hasTemplatesEnrolled) {
                return new Pair<>(firstHwAvailable,
                        BiometricConstants.BIOMETRIC_ERROR_NO_BIOMETRICS);
            } else if (!enabledForApps) {
                return new Pair<>(TYPE_NONE, BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE);
            if (biometricStatus == BIOMETRIC_OK) {
                return new Pair<>(biometricModality,
                        biometricStatusToBiometricConstant(biometricStatus));
            } else {
                Slog.e(TAG, "Unexpected case");
                return new Pair<>(TYPE_NONE, BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE);
                return new Pair<>(firstBiometricModality,
                        biometricStatusToBiometricConstant(firstBiometricStatus));
            }
        } else if (credentialRequested) {
            if (credentialOk) {
+8 −5
Original line number Diff line number Diff line
@@ -75,7 +75,7 @@ public class Utils {
     * @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) {
    public static boolean isCredentialRequested(@Authenticators.Types int authenticators) {
        return (authenticators & Authenticators.DEVICE_CREDENTIAL) != 0;
    }

@@ -83,8 +83,8 @@ public class Utils {
     * @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));
    public static boolean isCredentialRequested(Bundle bundle) {
        return isCredentialRequested(bundle.getInt(BiometricPrompt.KEY_AUTHENTICATORS_ALLOWED));
    }

    /**
@@ -115,7 +115,7 @@ public class Utils {
     * @param bundle should be first processed by {@link #combineAuthenticatorBundles(Bundle)}
     * @return true if biometric authentication is allowed.
     */
    public static boolean isBiometricAllowed(Bundle bundle) {
    public static boolean isBiometricRequested(Bundle bundle) {
        return getPublicBiometricStrength(bundle) != 0;
    }

@@ -164,7 +164,7 @@ public class Utils {
        // should be set.
        final int biometricBits = authenticators & Authenticators.BIOMETRIC_MIN_STRENGTH;
        if (biometricBits == Authenticators.EMPTY_SET
                && isDeviceCredentialAllowed(authenticators)) {
                && isCredentialRequested(authenticators)) {
            return true;
        } else if (biometricBits == Authenticators.BIOMETRIC_STRONG) {
            return true;
@@ -204,6 +204,9 @@ public class Utils {
            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;
            default:
                Slog.e(BiometricService.TAG, "Unhandled result code: " + biometricConstantsCode);
                biometricManagerCode = BiometricManager.BIOMETRIC_ERROR_HW_UNAVAILABLE;
+40 −5
Original line number Diff line number Diff line
@@ -283,7 +283,7 @@ public class BiometricServiceTest {
                null /* authenticators */);
        waitForIdle();
        verify(mReceiver1).onError(
                eq(BiometricAuthenticator.TYPE_NONE),
                eq(BiometricAuthenticator.TYPE_FINGERPRINT),
                eq(BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE),
                eq(0 /* vendorCode */));
    }
@@ -1117,14 +1117,14 @@ public class BiometricServiceTest {

        // STRONG-only auth is not available
        int authenticators = Authenticators.BIOMETRIC_STRONG;
        assertEquals(BiometricManager.BIOMETRIC_ERROR_NO_HARDWARE,
        assertEquals(BiometricManager.BIOMETRIC_ERROR_SECURITY_UPDATE_REQUIRED,
                invokeCanAuthenticate(mBiometricService, authenticators));
        invokeAuthenticate(mBiometricService.mImpl, mReceiver1, false /* requireConfirmation */,
                authenticators);
        waitForIdle();
        verify(mReceiver1).onError(
                eq(BiometricAuthenticator.TYPE_NONE),
                eq(BiometricPrompt.BIOMETRIC_ERROR_HW_NOT_PRESENT),
                eq(BiometricAuthenticator.TYPE_FINGERPRINT),
                eq(BiometricPrompt.BIOMETRIC_ERROR_SECURITY_UPDATE_REQUIRED),
                eq(0) /* vendorCode */);

        // Request for weak auth works
@@ -1154,7 +1154,7 @@ public class BiometricServiceTest {
                false /* requireConfirmation */,
                authenticators);
        waitForIdle();
        assertTrue(Utils.isDeviceCredentialAllowed(mBiometricService.mCurrentAuthSession.mBundle));
        assertTrue(Utils.isCredentialRequested(mBiometricService.mCurrentAuthSession.mBundle));
        verify(mBiometricService.mStatusBarService).showAuthenticationDialog(
                eq(mBiometricService.mCurrentAuthSession.mBundle),
                any(IBiometricServiceReceiverInternal.class),
@@ -1162,6 +1162,28 @@ public class BiometricServiceTest {
                anyBoolean() /* requireConfirmation */,
                anyInt() /* userId */,
                eq(TEST_PACKAGE_NAME));

        // Un-downgrading the authenticator allows successful strong auth
        for (BiometricService.AuthenticatorWrapper wrapper : mBiometricService.mAuthenticators) {
            if (wrapper.id == testId) {
                wrapper.updateStrength(Authenticators.BIOMETRIC_STRONG);
            }
        }

        resetReceiver();
        authenticators = Authenticators.BIOMETRIC_STRONG;
        assertEquals(BiometricManager.BIOMETRIC_SUCCESS,
                invokeCanAuthenticate(mBiometricService, authenticators));
        invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1,
                false /* requireConfirmation */, authenticators);
        waitForIdle();
        verify(mBiometricService.mStatusBarService).showAuthenticationDialog(
                eq(mBiometricService.mCurrentAuthSession.mBundle),
                any(IBiometricServiceReceiverInternal.class),
                eq(BiometricAuthenticator.TYPE_FINGERPRINT /* biometricModality */),
                anyBoolean() /* requireConfirmation */,
                anyInt() /* userId */,
                eq(TEST_PACKAGE_NAME));
    }

    @Test(expected = IllegalStateException.class)
@@ -1244,6 +1266,19 @@ public class BiometricServiceTest {
                BiometricService.STATE_AUTH_STARTED);
    }

    @Test
    public void testAuthentication_normalAppIgnoresDevicePolicy() throws Exception {
        setupAuthForOnly(BiometricAuthenticator.TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG);
        when(mDevicePolicyManager
                .getKeyguardDisabledFeatures(any() /* admin */, anyInt() /* userHandle */))
                .thenReturn(DevicePolicyManager.KEYGUARD_DISABLE_FINGERPRINT);
        invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1,
                false /* requireConfirmation */, Authenticators.BIOMETRIC_STRONG);
        waitForIdle();
        assertEquals(mBiometricService.mCurrentAuthSession.mState,
                BiometricService.STATE_AUTH_STARTED);
    }

    @Test
    public void testWorkAuthentication_faceWorksIfNotDisabledByDevicePolicyManager()
            throws Exception {
+9 −9
Original line number Diff line number Diff line
@@ -91,31 +91,31 @@ public class UtilsTest {
    @Test
    public void testIsDeviceCredentialAllowed_withIntegerFlags() {
        int authenticators = 0;
        assertFalse(Utils.isDeviceCredentialAllowed(authenticators));
        assertFalse(Utils.isCredentialRequested(authenticators));

        authenticators |= Authenticators.DEVICE_CREDENTIAL;
        assertTrue(Utils.isDeviceCredentialAllowed(authenticators));
        assertTrue(Utils.isCredentialRequested(authenticators));

        authenticators |= Authenticators.BIOMETRIC_WEAK;
        assertTrue(Utils.isDeviceCredentialAllowed(authenticators));
        assertTrue(Utils.isCredentialRequested(authenticators));
    }

    @Test
    public void testIsDeviceCredentialAllowed_withBundle() {
        Bundle bundle = new Bundle();
        assertFalse(Utils.isDeviceCredentialAllowed(bundle));
        assertFalse(Utils.isCredentialRequested(bundle));

        int authenticators = 0;
        bundle.putInt(BiometricPrompt.KEY_AUTHENTICATORS_ALLOWED, authenticators);
        assertFalse(Utils.isDeviceCredentialAllowed(bundle));
        assertFalse(Utils.isCredentialRequested(bundle));

        authenticators |= Authenticators.DEVICE_CREDENTIAL;
        bundle.putInt(BiometricPrompt.KEY_AUTHENTICATORS_ALLOWED, authenticators);
        assertTrue(Utils.isDeviceCredentialAllowed(bundle));
        assertTrue(Utils.isCredentialRequested(bundle));

        authenticators |= Authenticators.BIOMETRIC_WEAK;
        bundle.putInt(BiometricPrompt.KEY_AUTHENTICATORS_ALLOWED, authenticators);
        assertTrue(Utils.isDeviceCredentialAllowed(bundle));
        assertTrue(Utils.isCredentialRequested(bundle));
    }

    @Test
@@ -140,14 +140,14 @@ public class UtilsTest {
        for (int i = 0; i <= 7; i++) {
            int authenticators = 1 << i;
            bundle.putInt(BiometricPrompt.KEY_AUTHENTICATORS_ALLOWED, authenticators);
            assertTrue(Utils.isBiometricAllowed(bundle));
            assertTrue(Utils.isBiometricRequested(bundle));
        }

        // The rest of the bits are not allowed to integrate with the public APIs
        for (int i = 8; i < 32; i++) {
            int authenticators = 1 << i;
            bundle.putInt(BiometricPrompt.KEY_AUTHENTICATORS_ALLOWED, authenticators);
            assertFalse(Utils.isBiometricAllowed(bundle));
            assertFalse(Utils.isBiometricRequested(bundle));
        }
    }