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

Commit 755f66a6 authored by Kevin Chyn's avatar Kevin Chyn
Browse files

6/n: Key by Biometric ID instead of Modality

This change contains the following major changes:
1) Moves checkAndGetAuthenticators and most of its logic into
   a new modular class (PreAuthInfo), which has takes API parameters,
   biometric sensor status, user/system settings, etc and generates
   preliminary info for client requests (canAuthenticate
   authenticate APIs).
2) Sensor state during authentication sessions are now tracked
   within BiometricSensor (used to be floating around in
   BiometricService)
3) BiometricPrompt (above system server) is responsible for
   handling any combination of modality errors now. As such,
   updated selection logic and also added a catch-all string

Bug: 149067920

Test: atest com.android.server.biometrics
Test: CtsVerifier biometric section on face and fingerprint devices

Change-Id: I030593de2c350824bc961f92a75895f49a9c7996
parent 25a5680d
Loading
Loading
Loading
Loading
+44 −19
Original line number Diff line number Diff line
@@ -460,8 +460,7 @@ public class BiometricPrompt implements BiometricAuthenticator, BiometricConstan
            new IBiometricServiceReceiver.Stub() {

        @Override
        public void onAuthenticationSucceeded(@AuthenticationResultType int authenticationType)
                throws RemoteException {
        public void onAuthenticationSucceeded(@AuthenticationResultType int authenticationType) {
            mExecutor.execute(() -> {
                final AuthenticationResult result =
                        new AuthenticationResult(mCryptoObject, authenticationType);
@@ -470,42 +469,68 @@ public class BiometricPrompt implements BiometricAuthenticator, BiometricConstan
        }

        @Override
        public void onAuthenticationFailed() throws RemoteException {
        public void onAuthenticationFailed() {
            mExecutor.execute(() -> {
                mAuthenticationCallback.onAuthenticationFailed();
            });
        }

        @Override
        public void onError(int modality, int error, int vendorCode) throws RemoteException {
            mExecutor.execute(() -> {
                String errorMessage;
        public void onError(@BiometricAuthenticator.Modality int modality, int error,
                int vendorCode) {

            String errorMessage = null;
            switch (modality) {
                case TYPE_FACE:
                    errorMessage = FaceManager.getErrorString(mContext, error, vendorCode);
                    break;

                case TYPE_FINGERPRINT:
                        errorMessage = FingerprintManager.getErrorString(mContext, error,
                                vendorCode);
                    errorMessage = FingerprintManager.getErrorString(mContext, error, vendorCode);
                    break;
            }

            // Look for generic errors, as it may be a combination of modalities, or no modality
            // (e.g. attempted biometric authentication without biometric sensors).
            if (errorMessage == null) {
                switch (error) {
                    case BIOMETRIC_ERROR_CANCELED:
                        errorMessage = mContext.getString(R.string.biometric_error_canceled);
                        break;
                    case BIOMETRIC_ERROR_USER_CANCELED:
                        errorMessage = mContext.getString(R.string.biometric_error_user_canceled);
                        break;
                    case BIOMETRIC_ERROR_HW_NOT_PRESENT:
                        errorMessage = mContext.getString(R.string.biometric_error_hw_unavailable);
                        break;
                    case BIOMETRIC_ERROR_NO_DEVICE_CREDENTIAL:
                        errorMessage = mContext.getString(
                                R.string.biometric_error_device_not_secured);
                        break;
                    default:
                        errorMessage = "";
                        Log.e(TAG, "Unknown error, modality: " + modality
                                + " error: " + error
                                + " vendorCode: " + vendorCode);
                        errorMessage = mContext.getString(R.string.biometric_error_generic);
                        break;
                }
            }
                mAuthenticationCallback.onAuthenticationError(error, errorMessage);

            final String stringToSend = errorMessage;
            mExecutor.execute(() -> {
                mAuthenticationCallback.onAuthenticationError(error, stringToSend);
            });
        }

        @Override
        public void onAcquired(int acquireInfo, String message) throws RemoteException {
        public void onAcquired(int acquireInfo, String message) {
            mExecutor.execute(() -> {
                mAuthenticationCallback.onAuthenticationHelp(acquireInfo, message);
            });
        }

        @Override
        public void onDialogDismissed(int reason) throws RemoteException {
        public void onDialogDismissed(int reason) {
            // Check the reason and invoke OnClickListener(s) if necessary
            if (reason == DISMISSED_REASON_BIOMETRIC_CONFIRMED) {
                mPositiveButtonInfo.executor.execute(() -> {
@@ -519,7 +544,7 @@ public class BiometricPrompt implements BiometricAuthenticator, BiometricConstan
        }

        @Override
        public void onSystemEvent(int event) throws RemoteException {
        public void onSystemEvent(int event) {
            mExecutor.execute(() -> {
                mAuthenticationCallback.onSystemEvent(event);
            });
+2 −0
Original line number Diff line number Diff line
@@ -1488,6 +1488,8 @@
    <string name="biometric_error_canceled">Authentication canceled</string>
    <!-- Message returned to applications if BiometricPrompt setAllowDeviceCredentials is enabled but no pin, pattern, or password is set. [CHAR LIMIT=NONE] -->
    <string name="biometric_error_device_not_secured">No pin, pattern, or password set</string>
    <!-- Message returned to applications when an unexpected/unknown error occurs. [CHAR LIMIT=50]-->
    <string name="biometric_error_generic">Error authenticating</string>

    <!-- Message shown during fingerprint acquisision when the fingerprint cannot be recognized -->
    <string name="fingerprint_acquired_partial">Partial fingerprint detected. Please try again.</string>
+1 −0
Original line number Diff line number Diff line
@@ -2423,6 +2423,7 @@
  <java-symbol type="string" name="biometric_not_recognized" />
  <java-symbol type="string" name="biometric_error_canceled" />
  <java-symbol type="string" name="biometric_error_device_not_secured" />
  <java-symbol type="string" name="biometric_error_generic" />

  <!-- Fingerprint messages -->
  <java-symbol type="string" name="fingerprint_error_unable_to_process" />
+9 −9
Original line number Diff line number Diff line
@@ -327,13 +327,13 @@ public class AuthService extends SystemService {
    }

    private void registerAuthenticator(SensorConfig config) throws RemoteException {
        Slog.d(TAG, "Registering ID: " + config.mId
                + " Modality: " + config.mModality
                + " Strength: " + config.mStrength);
        Slog.d(TAG, "Registering ID: " + config.id
                + " Modality: " + config.modality
                + " Strength: " + config.strength);

        final IBiometricAuthenticator.Stub authenticator;

        switch (config.mModality) {
        switch (config.modality) {
            case TYPE_FINGERPRINT:
                final IFingerprintService fingerprintService = mInjector.getFingerprintService();
                if (fingerprintService == null) {
@@ -343,7 +343,7 @@ public class AuthService extends SystemService {
                }

                authenticator = new FingerprintAuthenticator(fingerprintService);
                fingerprintService.initConfiguredStrength(config.mStrength);
                fingerprintService.initConfiguredStrength(config.strength);
                break;

            case TYPE_FACE:
@@ -355,7 +355,7 @@ public class AuthService extends SystemService {
                }

                authenticator = new FaceAuthenticator(faceService);
                faceService.initConfiguredStrength(config.mStrength);
                faceService.initConfiguredStrength(config.strength);
                break;

            case TYPE_IRIS:
@@ -367,15 +367,15 @@ public class AuthService extends SystemService {
                }

                authenticator = new IrisAuthenticator(irisService);
                irisService.initConfiguredStrength(config.mStrength);
                irisService.initConfiguredStrength(config.strength);
                break;

            default:
                Slog.e(TAG, "Unknown modality: " + config.mModality);
                Slog.e(TAG, "Unknown modality: " + config.modality);
                return;
        }

        mBiometricService.registerAuthenticator(config.mId, config.mModality, config.mStrength,
        mBiometricService.registerAuthenticator(config.id, config.modality, config.strength,
                authenticator);
    }

+104 −22
Original line number Diff line number Diff line
@@ -16,12 +16,18 @@

package com.android.server.biometrics;

import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FACE;

import android.annotation.IntDef;
import android.hardware.biometrics.BiometricAuthenticator;
import android.hardware.biometrics.IBiometricServiceReceiver;
import android.hardware.biometrics.IBiometricServiceReceiverInternal;
import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Slog;

import java.util.HashMap;
import java.util.Random;

/**
 * Class that defines the states of an authentication session invoked via
@@ -29,6 +35,7 @@ import java.util.HashMap;
 * state information for such a session.
 */
final class AuthSession {
    private static final String TAG = "BiometricService/AuthSession";

    /**
     * Authentication either just called and we have not transitioned to the CALLED state, or
@@ -78,19 +85,15 @@ final class AuthSession {
            STATE_SHOWING_DEVICE_CREDENTIAL})
    @interface SessionState {}


    // Map of Authenticator/Cookie pairs. We expect to receive the cookies back from
    // <Biometric>Services before we can start authenticating. Pairs that have been returned
    // are moved to mModalitiesMatched.
    final HashMap<Integer, Integer> mModalitiesWaiting;
    // Pairs that have been matched.
    final HashMap<Integer, Integer> mModalitiesMatched = new HashMap<>();
    private final Random mRandom;
    final PreAuthInfo mPreAuthInfo;

    // The following variables are passed to authenticateInternal, which initiates the
    // appropriate <Biometric>Services.
    final IBinder mToken;
    final long mOperationId;
    final int mUserId;
    final IBiometricServiceReceiverInternal mInternalReceiver;
    // Original receiver from BiometricPrompt.
    final IBiometricServiceReceiver mClientReceiver;
    final String mOpPackageName;
@@ -99,9 +102,7 @@ final class AuthSession {
    final int mCallingUid;
    final int mCallingPid;
    final int mCallingUserId;
    // Continue authentication with the same modality/modalities after "try again" is
    // pressed
    final int mModality;

    final boolean mRequireConfirmation;

    // The current state, which can be either idle, called, or started
@@ -118,22 +119,89 @@ final class AuthSession {
    // Timestamp when hardware authentication occurred
    long mAuthenticatedTimeMs;

    AuthSession(HashMap<Integer, Integer> modalities, IBinder token, long operationId,
            int userId, IBiometricServiceReceiver receiver, String opPackageName,
    AuthSession(Random random, PreAuthInfo preAuthInfo, IBinder token, long operationId,
            int userId, IBiometricServiceReceiverInternal internalReceiver,
            IBiometricServiceReceiver clientReceiver, String opPackageName,
            Bundle bundle, int callingUid, int callingPid, int callingUserId,
            int modality, boolean requireConfirmation) {
        mModalitiesWaiting = modalities;
            boolean requireConfirmation) {
        mRandom = random;
        mPreAuthInfo = preAuthInfo;
        mToken = token;
        mOperationId = operationId;
        mUserId = userId;
        mClientReceiver = receiver;
        mInternalReceiver = internalReceiver;
        mClientReceiver = clientReceiver;
        mOpPackageName = opPackageName;
        mBundle = bundle;
        mCallingUid = callingUid;
        mCallingPid = callingPid;
        mCallingUserId = callingUserId;
        mModality = modality;
        mRequireConfirmation = requireConfirmation;

        setSensorsToStateUnknown();
    }

    /**
     * @return bitmask representing the modalities that are running or could be running for the
     * current session.
     */
    @BiometricAuthenticator.Modality int getEligibleModalities() {
        return mPreAuthInfo.getEligibleModalities();
    }

    void setSensorsToStateUnknown() {
        // Generate random cookies to pass to the services that should prepare to start
        // authenticating. Store the cookie here and wait for all services to "ack"
        // with the cookie. Once all cookies are received, we can show the prompt
        // and let the services start authenticating. The cookie should be non-zero.
        for (BiometricSensor sensor : mPreAuthInfo.eligibleSensors) {
            sensor.goToStateUnknown();
        }
    }

    void prepareAllSensorsForAuthentication() throws RemoteException {
        for (BiometricSensor sensor : mPreAuthInfo.eligibleSensors) {
            final int cookie = mRandom.nextInt(Integer.MAX_VALUE - 1) + 1;
            sensor.goToStateWaitingForCookie(mRequireConfirmation, mToken, mOperationId, mUserId,
                    mInternalReceiver, mOpPackageName, cookie, mCallingUid, mCallingPid,
                    mCallingUserId);
        }
    }

    void onCookieReceived(int cookie) {
        for (BiometricSensor sensor : mPreAuthInfo.eligibleSensors) {
            sensor.goToStateCookieReturnedIfCookieMatches(cookie);
        }
    }

    void startAllPreparedSensors() {
        for (BiometricSensor sensor : mPreAuthInfo.eligibleSensors) {
            try {
                sensor.startSensor();
            } catch (RemoteException e) {
                Slog.e(TAG, "Unable to start prepared client, sensor ID: "
                        + sensor.id, e);
            }
        }
    }

    void cancelAllSensors(boolean fromClient) {
        // TODO: For multiple modalities, send a single ERROR_CANCELED only when all
        // drivers have canceled authentication.
        for (BiometricSensor sensor : mPreAuthInfo.eligibleSensors) {
            try {
                sensor.goToStateCancelling(mToken, mOpPackageName, mCallingUid, mCallingPid,
                        mCallingUserId, fromClient);
            } catch (RemoteException e) {
                Slog.e(TAG, "Unable to cancel authentication");
            }
        }
    }

    void onErrorReceived(int cookie, int error) {
        for (BiometricSensor sensor : mPreAuthInfo.eligibleSensors) {
            sensor.goToStoppedStateIfCookieMatches(cookie, error);
        }
    }

    boolean isCrypto() {
@@ -141,11 +209,10 @@ final class AuthSession {
    }

    boolean containsCookie(int cookie) {
        if (mModalitiesWaiting != null && mModalitiesWaiting.containsValue(cookie)) {
        for (BiometricSensor sensor : mPreAuthInfo.eligibleSensors) {
            if (sensor.getCookie() == cookie) {
                return true;
            }
        if (mModalitiesMatched != null && mModalitiesMatched.containsValue(cookie)) {
            return true;
        }
        return false;
    }
@@ -153,4 +220,19 @@ final class AuthSession {
    boolean isAllowDeviceCredential() {
        return Utils.isCredentialRequested(mBundle);
    }

    boolean allCookiesReceived() {
        final int remainingCookies = mPreAuthInfo.numSensorsWaitingForCookie();
        Slog.d(TAG, "Remaining cookies: " + remainingCookies);
        return remainingCookies == 0;
    }

    boolean hasPausableBiometric() {
        for (BiometricSensor sensor : mPreAuthInfo.eligibleSensors) {
            if (sensor.modality == TYPE_FACE) {
                return true;
            }
        }
        return false;
    }
}
Loading