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

Commit 5945a4b8 authored by Joshua McCloskey's avatar Joshua McCloskey
Browse files

Integrate Session Coordinator

Test: atest AuthResultCoordinatorTest AuthSessionCoordinatorTest MultiBiometricLockoutStateTest
Test: Verified auth with strong & convenient biometric.
Test: Verified auth with strong & weak biometric.
Bug: 244355277
Change-Id: I38ea11852fa3052a13d955f1de597d8428878821
parent 51d34cca
Loading
Loading
Loading
Loading
+20 −0
Original line number Diff line number Diff line
@@ -212,6 +212,26 @@ public class BiometricManager {
         * @see android.security.keystore.KeyGenParameterSpec.Builder
         */
        int DEVICE_CREDENTIAL = 1 << 15;

    }

    /**
     * @hide
     * returns a string representation of an authenticator type.
     */
    @NonNull public static String authenticatorToStr(@Authenticators.Types int authenticatorType) {
        switch(authenticatorType) {
            case Authenticators.BIOMETRIC_STRONG:
                return "BIOMETRIC_STRONG";
            case Authenticators.BIOMETRIC_WEAK:
                return "BIOMETRIC_WEAK";
            case Authenticators.BIOMETRIC_CONVENIENCE:
                return "BIOMETRIC_CONVENIENCE";
            case Authenticators.DEVICE_CREDENTIAL:
                return "DEVICE_CREDENTIAL";
            default:
                return "Unknown authenticator type: " + authenticatorType;
        }
    }

    /**
+7 −0
Original line number Diff line number Diff line
@@ -21,11 +21,15 @@ import android.annotation.Nullable;
import android.content.Context;
import android.hardware.biometrics.common.OperationContext;

import com.android.server.biometrics.sensors.AuthSessionCoordinator;

import java.util.function.Consumer;

/**
 * Cache for system state not directly related to biometric operations that is used for
 * logging or optimizations.
 *
 * This class is also used to inject dependencies such as {@link AuthSessionCoordinator}
 */
public interface BiometricContext {
    /** Gets the context source from the system context. */
@@ -59,4 +63,7 @@ public interface BiometricContext {

    /** Unsubscribe from context changes. */
    void unsubscribe(@NonNull OperationContext context);

    /** Obtains an AuthSessionCoordinator. */
    AuthSessionCoordinator getAuthSessionCoordinator();
}
+12 −2
Original line number Diff line number Diff line
@@ -35,6 +35,7 @@ import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.logging.InstanceId;
import com.android.internal.statusbar.ISessionListener;
import com.android.internal.statusbar.IStatusBarService;
import com.android.server.biometrics.sensors.AuthSessionCoordinator;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
@@ -59,7 +60,8 @@ final class BiometricContextProvider implements BiometricContext {
                    sInstance = new BiometricContextProvider(
                            new AmbientDisplayConfiguration(context),
                            IStatusBarService.Stub.asInterface(ServiceManager.getServiceOrThrow(
                                    Context.STATUS_BAR_SERVICE)), null /* handler */);
                                    Context.STATUS_BAR_SERVICE)), null /* handler */,
                            new AuthSessionCoordinator());
                } catch (ServiceNotFoundException e) {
                    throw new IllegalStateException("Failed to find required service", e);
                }
@@ -76,13 +78,16 @@ final class BiometricContextProvider implements BiometricContext {
    private final Map<Integer, InstanceId> mSession = new ConcurrentHashMap<>();

    private final AmbientDisplayConfiguration mAmbientDisplayConfiguration;
    private final AuthSessionCoordinator mAuthSessionCoordinator;
    private boolean mIsAod = false;
    private boolean mIsAwake = false;

    @VisibleForTesting
    BiometricContextProvider(@NonNull AmbientDisplayConfiguration ambientDisplayConfiguration,
            @NonNull IStatusBarService service, @Nullable Handler handler) {
            @NonNull IStatusBarService service, @Nullable Handler handler,
            AuthSessionCoordinator authSessionCoordinator) {
        mAmbientDisplayConfiguration = ambientDisplayConfiguration;
        mAuthSessionCoordinator = authSessionCoordinator;
        try {
            service.setBiometicContextListener(new IBiometricContextListener.Stub() {
                @Override
@@ -190,6 +195,11 @@ final class BiometricContextProvider implements BiometricContext {
        mSubscribers.remove(context);
    }

    @Override
    public AuthSessionCoordinator getAuthSessionCoordinator() {
        return mAuthSessionCoordinator;
    }

    private void notifySubscribers() {
        mSubscribers.forEach((context, consumer) -> {
            context.isAod = isAod();
+44 −42
Original line number Diff line number Diff line
@@ -17,9 +17,11 @@
package com.android.server.biometrics.sensors;

import android.hardware.biometrics.BiometricManager.Authenticators;
import android.util.ArrayMap;

import java.util.ArrayList;
import java.util.List;
import java.util.Collections;
import java.util.Map;
import java.util.function.IntFunction;

/**
 * A class that takes in a series of authentication attempts (successes, failures, lockouts)
@@ -30,64 +32,64 @@ import java.util.List;
 */
class AuthResultCoordinator {

    /**
     * Indicates no change has occurred with this authenticator.
     */
    static final int AUTHENTICATOR_DEFAULT = 0;
    /**
     * Indicated this authenticator has received a lockout.
     */
    static final int AUTHENTICATOR_LOCKED = 1 << 0;
    /**
     * Indicates this authenticator has received a successful unlock.
     */
    static final int AUTHENTICATOR_UNLOCKED = 1 << 1;
    private static final String TAG = "AuthResultCoordinator";
    private final List<AuthResult> mOperations;
    private final Map<Integer, Integer> mAuthenticatorState;

    AuthResultCoordinator() {
        mOperations = new ArrayList<>();
        mAuthenticatorState = new ArrayMap<>();
        mAuthenticatorState.put(Authenticators.BIOMETRIC_STRONG, AUTHENTICATOR_DEFAULT);
        mAuthenticatorState.put(Authenticators.BIOMETRIC_WEAK, AUTHENTICATOR_DEFAULT);
        mAuthenticatorState.put(Authenticators.BIOMETRIC_CONVENIENCE, AUTHENTICATOR_DEFAULT);
    }

    /**
     * Adds auth success for a given strength to the current operation list.
     */
    void authenticatedFor(@Authenticators.Types int strength) {
        mOperations.add(new AuthResult(AuthResult.AUTHENTICATED, strength));
    private void updateState(@Authenticators.Types int strength, IntFunction<Integer> mapper) {
        switch (strength) {
            case Authenticators.BIOMETRIC_STRONG:
                mAuthenticatorState.put(Authenticators.BIOMETRIC_STRONG,
                        mapper.apply(mAuthenticatorState.get(Authenticators.BIOMETRIC_STRONG)));
                // fall through
            case Authenticators.BIOMETRIC_WEAK:
                mAuthenticatorState.put(Authenticators.BIOMETRIC_WEAK,
                        mapper.apply(mAuthenticatorState.get(Authenticators.BIOMETRIC_WEAK)));
                // fall through
            case Authenticators.BIOMETRIC_CONVENIENCE:
                mAuthenticatorState.put(Authenticators.BIOMETRIC_CONVENIENCE,
                        mapper.apply(
                                mAuthenticatorState.get(Authenticators.BIOMETRIC_CONVENIENCE)));
        }
    }

    /**
     * Adds auth ended for a given strength to the current operation list.
     * Adds auth success for a given strength to the current operation list.
     */
    void authEndedFor(@Authenticators.Types int strength) {
        mOperations.add(new AuthResult(AuthResult.FAILED, strength));
    void authenticatedFor(@Authenticators.Types int strength) {
        updateState(strength, (old) -> AUTHENTICATOR_UNLOCKED | old);
    }

    /**
     * Adds a lock out of a given strength to the current operation list.
     */
    void lockedOutFor(@Authenticators.Types int strength) {
        mOperations.add(new AuthResult(AuthResult.LOCKED_OUT, strength));
        updateState(strength, (old) -> AUTHENTICATOR_LOCKED | old);
    }

    /**
     * Obtains an auth result & strength from a current set of biometric operations.
     * Returns the current authenticator state. Each authenticator will have
     * the associated operations that were performed on them(DEFAULT, LOCKED, UNLOCKED).
     */
    AuthResult getResult() {
        AuthResult result = new AuthResult(AuthResult.FAILED, Authenticators.BIOMETRIC_CONVENIENCE);
        return mOperations.stream().filter(
                (element) -> element.getStatus() != AuthResult.FAILED).reduce(result,
                ((curr, next) -> {
                    int strengthCompare = curr.getBiometricStrength() - next.getBiometricStrength();
                    if (strengthCompare < 0) {
                        return curr;
                    } else if (strengthCompare == 0) {
                        // Equal level of strength, favor authentication.
                        if (curr.getStatus() == AuthResult.AUTHENTICATED) {
                            return curr;
                        } else {
                            // Either next is Authenticated, or it is not, either way return this
                            // one.
                            return next;
    final Map<Integer, Integer> getResult() {
        return Collections.unmodifiableMap(mAuthenticatorState);
    }
                    } else {
                        // curr is a weaker biometric
                        return next;
}
                }));
    }

    void resetState() {
        mOperations.clear();
    }
}

+138 −28
Original line number Diff line number Diff line
@@ -16,10 +16,22 @@

package com.android.server.biometrics.sensors;

import static com.android.server.biometrics.sensors.AuthResultCoordinator.AUTHENTICATOR_LOCKED;
import static com.android.server.biometrics.sensors.AuthResultCoordinator.AUTHENTICATOR_UNLOCKED;

import android.hardware.biometrics.BiometricManager.Authenticators;
import android.os.SystemClock;
import android.util.Pair;
import android.util.Slog;

import com.android.internal.annotations.VisibleForTesting;

import java.time.Clock;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
@@ -28,20 +40,31 @@ import java.util.Set;
 * This class is not thread-safe. In general, all calls to this class should be made on the same
 * handler to ensure no collisions.
 */
class AuthSessionCoordinator implements AuthSessionListener {
public class AuthSessionCoordinator implements AuthSessionListener {
    private static final String TAG = "AuthSessionCoordinator";

    private final Set<Integer> mAuthOperations;
    private final MultiBiometricLockoutState mMultiBiometricLockoutState;
    private final List<Pair<Integer, Long>> mTimedLockouts;
    private final RingBuffer mRingBuffer;
    private final Clock mClock;

    private int mUserId;
    private boolean mIsAuthenticating;
    private AuthResultCoordinator mAuthResultCoordinator;
    private MultiBiometricLockoutState mMultiBiometricLockoutState;

    AuthSessionCoordinator() {
    public AuthSessionCoordinator() {
        this(SystemClock.currentNetworkTimeClock());
    }

    @VisibleForTesting
    AuthSessionCoordinator(Clock clock) {
        mAuthOperations = new HashSet<>();
        mAuthResultCoordinator = new AuthResultCoordinator();
        mMultiBiometricLockoutState = new MultiBiometricLockoutState();
        mMultiBiometricLockoutState = new MultiBiometricLockoutState(clock);
        mRingBuffer = new RingBuffer(100);
        mTimedLockouts = new ArrayList<>();
        mClock = clock;
    }

    /**
@@ -51,7 +74,9 @@ class AuthSessionCoordinator implements AuthSessionListener {
        mAuthOperations.clear();
        mUserId = userId;
        mIsAuthenticating = true;
        mAuthResultCoordinator.resetState();
        mAuthOperations.clear();
        mAuthResultCoordinator = new AuthResultCoordinator();
        mRingBuffer.addApiCall("internal : onAuthSessionStarted(" + userId + ")");
    }

    /**
@@ -64,14 +89,27 @@ class AuthSessionCoordinator implements AuthSessionListener {
    void endAuthSession() {
        if (mIsAuthenticating) {
            mAuthOperations.clear();
            AuthResult res =
                    mAuthResultCoordinator.getResult();
            if (res.getStatus() == AuthResult.AUTHENTICATED) {
                mMultiBiometricLockoutState.onUserUnlocked(mUserId, res.getBiometricStrength());
            } else if (res.getStatus() == AuthResult.LOCKED_OUT) {
                mMultiBiometricLockoutState.onUserLocked(mUserId, res.getBiometricStrength());
            }
            mAuthResultCoordinator.resetState();
            final long currentTime = mClock.millis();
            for (Pair<Integer, Long> timedLockouts : mTimedLockouts) {
                mMultiBiometricLockoutState.increaseLockoutTime(mUserId, timedLockouts.first,
                        timedLockouts.second + currentTime);
            }
            // User unlocks can also unlock timed lockout Authenticator.Types
            final Map<Integer, Integer> result = mAuthResultCoordinator.getResult();
            for (int authenticator : Arrays.asList(Authenticators.BIOMETRIC_CONVENIENCE,
                    Authenticators.BIOMETRIC_WEAK, Authenticators.BIOMETRIC_STRONG)) {
                final Integer value = result.get(authenticator);
                if ((value & AUTHENTICATOR_UNLOCKED) == AUTHENTICATOR_UNLOCKED) {
                    mMultiBiometricLockoutState.setAuthenticatorTo(mUserId, authenticator,
                            true /* canAuthenticate */);
                    mMultiBiometricLockoutState.clearLockoutTime(mUserId, authenticator);
                } else if ((value & AUTHENTICATOR_LOCKED) == AUTHENTICATOR_LOCKED) {
                    mMultiBiometricLockoutState.setAuthenticatorTo(mUserId, authenticator,
                            false /* canAuthenticate */);
                }

            }
            mRingBuffer.addApiCall("internal : onAuthSessionEnded(" + mUserId + ")");
            mIsAuthenticating = false;
        }
    }
@@ -79,12 +117,15 @@ class AuthSessionCoordinator implements AuthSessionListener {
    /**
     * @return true if a user can authenticate with a given strength.
     */
    boolean getCanAuthFor(int userId, @Authenticators.Types int strength) {
    public boolean getCanAuthFor(int userId, @Authenticators.Types int strength) {
        return mMultiBiometricLockoutState.canUserAuthenticate(userId, strength);
    }

    @Override
    public void authStartedFor(int userId, int sensorId) {
    public void authStartedFor(int userId, int sensorId, long requestId) {
        mRingBuffer.addApiCall(
                "authStartedFor(userId=" + userId + ", sensorId=" + sensorId + ", requestId="
                        + requestId + ")");
        if (!mIsAuthenticating) {
            onAuthSessionStarted(userId);
        }
@@ -105,34 +146,58 @@ class AuthSessionCoordinator implements AuthSessionListener {

    @Override
    public void authenticatedFor(int userId, @Authenticators.Types int biometricStrength,
            int sensorId) {
            int sensorId, long requestId) {
        final String authStr =
                "authenticatedFor(userId=" + userId + ", strength=" + biometricStrength
                        + " , sensorId=" + sensorId + ", requestId= " + requestId + ")";
        mRingBuffer.addApiCall(authStr);
        mAuthResultCoordinator.authenticatedFor(biometricStrength);
        attemptToFinish(userId, sensorId,
                "authenticatedFor(userId=" + userId + ", biometricStrength=" + biometricStrength
                        + ", sensorId=" + sensorId + "");
        attemptToFinish(userId, sensorId, authStr);
    }

    @Override
    public void lockedOutFor(int userId, @Authenticators.Types int biometricStrength,
            int sensorId) {
        mAuthResultCoordinator.lockedOutFor(biometricStrength);
        attemptToFinish(userId, sensorId,
            int sensorId, long requestId) {
        final String lockedOutStr =
                "lockOutFor(userId=" + userId + ", biometricStrength=" + biometricStrength
                        + ", sensorId=" + sensorId + "");
                        + ", sensorId=" + sensorId + ", requestId=" + requestId + ")";
        mRingBuffer.addApiCall(lockedOutStr);
        mAuthResultCoordinator.lockedOutFor(biometricStrength);
        attemptToFinish(userId, sensorId, lockedOutStr);
    }

    @Override
    public void lockOutTimed(int userId, @Authenticators.Types int biometricStrength, int sensorId,
            long time, long requestId) {
        final String lockedOutStr =
                "lockOutTimedFor(userId=" + userId + ", biometricStrength=" + biometricStrength
                        + ", sensorId=" + sensorId + "time=" + time + ", requestId=" + requestId
                        + ")";
        mRingBuffer.addApiCall(lockedOutStr);
        mTimedLockouts.add(new Pair<>(biometricStrength, time));
        attemptToFinish(userId, sensorId, lockedOutStr);
    }

    @Override
    public void authEndedFor(int userId, @Authenticators.Types int biometricStrength,
            int sensorId) {
        mAuthResultCoordinator.authEndedFor(biometricStrength);
        attemptToFinish(userId, sensorId,
            int sensorId, long requestId) {
        final String authEndedStr =
                "authEndedFor(userId=" + userId + " ,biometricStrength=" + biometricStrength
                        + ", sensorId=" + sensorId);
                        + ", sensorId=" + sensorId + ", requestId=" + requestId + ")";
        mRingBuffer.addApiCall(authEndedStr);
        attemptToFinish(userId, sensorId, authEndedStr);
    }

    @Override
    public void resetLockoutFor(int userId, @Authenticators.Types int biometricStrength) {
        mMultiBiometricLockoutState.onUserUnlocked(userId, biometricStrength);
    public void resetLockoutFor(int userId, @Authenticators.Types int biometricStrength,
            long requestId) {
        final String resetLockStr =
                "resetLockoutFor(userId=" + userId + " ,biometricStrength=" + biometricStrength
                        + ", requestId=" + requestId + ")";
        mRingBuffer.addApiCall(resetLockStr);
        mMultiBiometricLockoutState.setAuthenticatorTo(userId, biometricStrength,
                true /*canAuthenticate */);
        mMultiBiometricLockoutState.clearLockoutTime(userId, biometricStrength);
    }

    private void attemptToFinish(int userId, int sensorId, String description) {
@@ -154,4 +219,49 @@ class AuthSessionCoordinator implements AuthSessionListener {
        }
    }

    /**
     * Returns a string representation of the past N API calls as well as the
     * permanent and timed lockout states for each user's authenticators.
     */
    @Override
    public String toString() {
        return mRingBuffer + "\n" + mMultiBiometricLockoutState;
    }

    private static class RingBuffer {
        private final String[] mApiCalls;
        private final int mSize;
        private int mCurr;
        private int mApiCallNumber;

        RingBuffer(int size) {
            if (size <= 0) {
                Slog.wtf(TAG, "Cannot initialize ring buffer of size: " + size);
            }
            mApiCalls = new String[size];
            mCurr = 0;
            mSize = size;
            mApiCallNumber = 0;
        }

        void addApiCall(String str) {
            mApiCalls[mCurr] = str;
            mCurr++;
            mCurr %= mSize;
            mApiCallNumber++;
        }

        @Override
        public String toString() {
            String buffer = "";
            int apiCall = mApiCallNumber > mSize ? mApiCallNumber - mSize : 0;
            for (int i = 0; i < mSize; i++) {
                final int location = (mCurr + i) % mSize;
                if (mApiCalls[location] != null) {
                    buffer += String.format("#%-5d %s\n", apiCall++, mApiCalls[location]);
                }
            }
            return buffer;
        }
    }
}
Loading