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

Commit 51d34cca authored by Joshua McCloskey's avatar Joshua McCloskey
Browse files

Adding lockout coordinator

Adding the building blocks for the lockout coordinator. This
class will be used to manage & coordinate lockouts/resetLockouts across
users/biometrics.

Test: atest AuthResultCoordinatorTest AuthSessionCoordinatorTest
MultiBiometricLockoutStateTest
Bug: 244355277

Change-Id: I1ffbd97eb06687f83f85f0c7b57a1bef65e0b141
parent f411c0c1
Loading
Loading
Loading
Loading
+40 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2022 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.sensors;

import android.hardware.biometrics.BiometricManager;

class AuthResult {
    static final int FAILED = 0;
    static final int LOCKED_OUT = 1;
    static final int AUTHENTICATED = 2;
    private final int mStatus;
    private final int mBiometricStrength;

    AuthResult(int status, @BiometricManager.Authenticators.Types int strength) {
        mStatus = status;
        mBiometricStrength = strength;
    }

    int getStatus() {
        return mStatus;
    }

    int getBiometricStrength() {
        return mBiometricStrength;
    }
}
+93 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2022 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.sensors;

import android.hardware.biometrics.BiometricManager.Authenticators;

import java.util.ArrayList;
import java.util.List;

/**
 * A class that takes in a series of authentication attempts (successes, failures, lockouts)
 * across different biometric strengths (convenience, weak, strong) and returns a single AuthResult.
 *
 * The AuthResult will be the strongest biometric operation that occurred amongst all reported
 * operations, and if multiple such operations exist, it will favor a successful authentication.
 */
class AuthResultCoordinator {

    private static final String TAG = "AuthResultCoordinator";
    private final List<AuthResult> mOperations;

    AuthResultCoordinator() {
        mOperations = new ArrayList<>();
    }

    /**
     * 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));
    }

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

    /**
     * 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));
    }

    /**
     * Obtains an auth result & strength from a current set of biometric operations.
     */
    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;
                        }
                    } else {
                        // curr is a weaker biometric
                        return next;
                    }
                }));
    }

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

+157 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2022 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.sensors;

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

import java.util.HashSet;
import java.util.Set;

/**
 * Coordinates lockout counter enforcement for all types of biometric strengths across all users.
 *
 * 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 {
    private static final String TAG = "AuthSessionCoordinator";

    private final Set<Integer> mAuthOperations;

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

    AuthSessionCoordinator() {
        mAuthOperations = new HashSet<>();
        mAuthResultCoordinator = new AuthResultCoordinator();
        mMultiBiometricLockoutState = new MultiBiometricLockoutState();
    }

    /**
     * A Call indicating that an auth session has started
     */
    void onAuthSessionStarted(int userId) {
        mAuthOperations.clear();
        mUserId = userId;
        mIsAuthenticating = true;
        mAuthResultCoordinator.resetState();
    }

    /**
     * Ends the current auth session and updates the lockout state.
     *
     * This can happen two ways.
     * 1. Manually calling this API
     * 2. If authStartedFor() was called, and all authentication attempts finish.
     */
    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();
            mIsAuthenticating = false;
        }
    }

    /**
     * @return true if a user can authenticate with a given strength.
     */
    boolean getCanAuthFor(int userId, @Authenticators.Types int strength) {
        return mMultiBiometricLockoutState.canUserAuthenticate(userId, strength);
    }

    @Override
    public void authStartedFor(int userId, int sensorId) {
        if (!mIsAuthenticating) {
            onAuthSessionStarted(userId);
        }

        if (mAuthOperations.contains(sensorId)) {
            Slog.e(TAG, "Error, authStartedFor(" + sensorId + ") without being finished");
            return;
        }

        if (mUserId != userId) {
            Slog.e(TAG, "Error authStartedFor(" + userId + ") Incorrect userId, expected" + mUserId
                    + ", ignoring...");
            return;
        }

        mAuthOperations.add(sensorId);
    }

    @Override
    public void authenticatedFor(int userId, @Authenticators.Types int biometricStrength,
            int sensorId) {
        mAuthResultCoordinator.authenticatedFor(biometricStrength);
        attemptToFinish(userId, sensorId,
                "authenticatedFor(userId=" + userId + ", biometricStrength=" + biometricStrength
                        + ", sensorId=" + sensorId + "");
    }

    @Override
    public void lockedOutFor(int userId, @Authenticators.Types int biometricStrength,
            int sensorId) {
        mAuthResultCoordinator.lockedOutFor(biometricStrength);
        attemptToFinish(userId, sensorId,
                "lockOutFor(userId=" + userId + ", biometricStrength=" + biometricStrength
                        + ", sensorId=" + sensorId + "");
    }

    @Override
    public void authEndedFor(int userId, @Authenticators.Types int biometricStrength,
            int sensorId) {
        mAuthResultCoordinator.authEndedFor(biometricStrength);
        attemptToFinish(userId, sensorId,
                "authEndedFor(userId=" + userId + " ,biometricStrength=" + biometricStrength
                        + ", sensorId=" + sensorId);
    }

    @Override
    public void resetLockoutFor(int userId, @Authenticators.Types int biometricStrength) {
        mMultiBiometricLockoutState.onUserUnlocked(userId, biometricStrength);
    }

    private void attemptToFinish(int userId, int sensorId, String description) {
        boolean didFail = false;
        if (!mAuthOperations.contains(sensorId)) {
            Slog.e(TAG, "Error unable to find auth operation : " + description);
            didFail = true;
        }
        if (userId != mUserId) {
            Slog.e(TAG, "Error mismatched userId, expected=" + mUserId + " for " + description);
            didFail = true;
        }
        if (didFail) {
            return;
        }
        mAuthOperations.remove(sensorId);
        if (mIsAuthenticating && mAuthOperations.isEmpty()) {
            endAuthSession();
        }
    }

}
+49 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2022 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.sensors;

import android.hardware.biometrics.BiometricManager.Authenticators;

/**
 * An interface that listens to authentication events.
 */
interface AuthSessionListener {
    /**
     * Indicates an auth operation has started for a given user and sensor.
     */
    void authStartedFor(int userId, int sensorId);

    /**
     * Indicates a successful authentication occurred for a sensor of a given strength.
     */
    void authenticatedFor(int userId, @Authenticators.Types int biometricStrength, int sensorId);

    /**
     * Indicates authentication ended for a sensor of a given strength.
     */
    void authEndedFor(int userId, @Authenticators.Types int biometricStrength, int sensorId);

    /**
     * Indicates a lockout occurred for a sensor of a given strength.
     */
    void lockedOutFor(int userId, @Authenticators.Types int biometricStrength, int sensorId);

    /**
     * Indicates that a reset lockout has happened for a given strength.
     */
    void resetLockoutFor(int uerId, @Authenticators.Types int biometricStrength);
}
+117 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2022 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.sensors;

import static android.hardware.biometrics.BiometricManager.Authenticators;
import static android.hardware.biometrics.BiometricManager.Authenticators.BIOMETRIC_CONVENIENCE;
import static android.hardware.biometrics.BiometricManager.Authenticators.BIOMETRIC_STRONG;
import static android.hardware.biometrics.BiometricManager.Authenticators.BIOMETRIC_WEAK;

import android.util.ArrayMap;
import android.util.Slog;

import com.android.internal.annotations.VisibleForTesting;

import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * This class is used as a system to store the state of each
 * {@link Authenticators.Types} status for every user.
 */
class MultiBiometricLockoutState {

    private static final String TAG = "MultiBiometricLockoutState";
    private static final Map<Integer, List<Integer>> PRECEDENCE;

    static {
        Map<Integer, List<Integer>> precedence = new ArrayMap<>();
        precedence.put(Authenticators.BIOMETRIC_STRONG,
                Arrays.asList(BIOMETRIC_STRONG, BIOMETRIC_WEAK, BIOMETRIC_CONVENIENCE));
        precedence.put(BIOMETRIC_WEAK, Arrays.asList(BIOMETRIC_WEAK, BIOMETRIC_CONVENIENCE));
        precedence.put(BIOMETRIC_CONVENIENCE, Arrays.asList(BIOMETRIC_CONVENIENCE));
        PRECEDENCE = Collections.unmodifiableMap(precedence);
    }

    private final Map<Integer, Map<Integer, Boolean>> mCanUserAuthenticate;

    @VisibleForTesting
    MultiBiometricLockoutState() {
        mCanUserAuthenticate = new HashMap<>();
    }

    private static Map<Integer, Boolean> createLockedOutMap() {
        Map<Integer, Boolean> lockOutMap = new HashMap<>();
        lockOutMap.put(BIOMETRIC_STRONG, false);
        lockOutMap.put(BIOMETRIC_WEAK, false);
        lockOutMap.put(BIOMETRIC_CONVENIENCE, false);
        return lockOutMap;
    }

    private Map<Integer, Boolean> getAuthMapForUser(int userId) {
        if (!mCanUserAuthenticate.containsKey(userId)) {
            mCanUserAuthenticate.put(userId, createLockedOutMap());
        }
        return mCanUserAuthenticate.get(userId);
    }

    /**
     * Indicates a {@link Authenticators} has been locked for userId.
     *
     * @param userId   The user.
     * @param strength The strength of biometric that is requested to be locked.
     */
    void onUserLocked(int userId, @Authenticators.Types int strength) {
        Slog.d(TAG, "onUserLocked(userId=" + userId + ", strength=" + strength + ")");
        Map<Integer, Boolean> canUserAuthState = getAuthMapForUser(userId);
        for (int strengthToLockout : PRECEDENCE.get(strength)) {
            canUserAuthState.put(strengthToLockout, false);
        }
    }

    /**
     * Indicates that a user has unlocked a {@link Authenticators}
     *
     * @param userId   The user.
     * @param strength The strength of biometric that is unlocked.
     */
    void onUserUnlocked(int userId, @Authenticators.Types int strength) {
        Slog.d(TAG, "onUserUnlocked(userId=" + userId + ", strength=" + strength + ")");
        Map<Integer, Boolean> canUserAuthState = getAuthMapForUser(userId);
        for (int strengthToLockout : PRECEDENCE.get(strength)) {
            canUserAuthState.put(strengthToLockout, true);
        }
    }

    /**
     * Indicates if a user can perform an authentication operation with a given
     * {@link Authenticators.Types}
     *
     * @param userId   The user.
     * @param strength The strength of biometric that is requested to authenticate.
     * @return If a user can authenticate with a given biometric of this strength.
     */
    boolean canUserAuthenticate(int userId, @Authenticators.Types int strength) {
        final boolean canAuthenticate = getAuthMapForUser(userId).get(strength);
        Slog.d(TAG, "canUserAuthenticate(userId=" + userId + ", strength=" + strength + ") ="
                + canAuthenticate);
        return canAuthenticate;
    }
}
Loading