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

Commit d9fc85ac authored by Andres Morales's avatar Andres Morales
Browse files

Add challenge to IGateKeeperService

required for enrolling secondary auth form-factors

Change-Id: Id5a1eb1ed22f01fbaabe8e4ebddfc42d58322625
parent 8fa5665f
Loading
Loading
Loading
Loading
+16 −2
Original line number Diff line number Diff line
@@ -45,7 +45,21 @@ interface IGateKeeperService {
     * @param enrolledPasswordHandle The handle against which the provided password will be
     *                               verified.
     * @param The plaintext blob to verify against enrolledPassword.
     * @return true if success, false if failure
     * @return True if the authentication was successful
     */
    boolean verify(int uid, in byte[] enrolledPasswordHandle, in byte[] providedPassword);
    boolean verify(int uid, in byte[] enrolledPasswordHandle,
            in byte[] providedPassword);
    /**
     * Verifies an enrolled handle against a provided, plaintext blob.
     * @param uid The Android user ID associated to this enrollment
     * @param challenge a challenge to authenticate agaisnt the device credential. If successful
     *                  authentication occurs, this value will be written to the returned 
     *                  authentication attestation.
     * @param enrolledPasswordHandle The handle against which the provided password will be
     *                               verified.
     * @param The plaintext blob to verify against enrolledPassword.
     * @return an opaque attestation of authentication on success, or null.
     */
    byte[] verifyChallenge(int uid, long challenge, in byte[] enrolledPasswordHandle, 
            in byte[] providedPassword);
}
+2 −0
Original line number Diff line number Diff line
@@ -26,8 +26,10 @@ interface ILockSettings {
    String getString(in String key, in String defaultValue, in int userId);
    void setLockPattern(in String pattern, in String savedPattern, int userId);
    boolean checkPattern(in String pattern, int userId);
    byte[] verifyPattern(in String pattern, long challenge, int userId);
    void setLockPassword(in String password, in String savedPassword, int userId);
    boolean checkPassword(in String password, int userId);
    byte[] verifyPassword(in String password, long challenge, int userId);
    boolean checkVoldPassword(int userId);
    boolean havePattern(int userId);
    boolean havePassword(int userId);
+36 −0
Original line number Diff line number Diff line
@@ -279,6 +279,24 @@ public class LockPatternUtils {
        }
    }

    /**
     * Check to see if a pattern matches the saved pattern.
     * If pattern matches, return an opaque attestation that the challenge
     * was verified.
     *
     * @param pattern The pattern to check.
     * @param challenge The challenge to verify against the pattern
     * @return the attestation that the challenge was verified, or null.
     */
    public byte[] verifyPattern(List<LockPatternView.Cell> pattern, long challenge) {
        final int userId = getCurrentOrCallingUserId();
        try {
            return getLockSettings().verifyPattern(patternToString(pattern), challenge, userId);
        } catch (RemoteException re) {
            return null;
        }
    }

    /**
     * Check to see if a pattern matches the saved pattern.  If no pattern exists,
     * always returns true.
@@ -294,6 +312,24 @@ public class LockPatternUtils {
        }
    }

    /**
     * Check to see if a password matches the saved password.
     * If password matches, return an opaque attestation that the challenge
     * was verified.
     *
     * @param password The password to check.
     * @param challenge The challenge to verify against the password
     * @return the attestation that the challenge was verified, or null.
     */
    public byte[] verifyPassword(String password, long challenge) {
        final int userId = getCurrentOrCallingUserId();
        try {
            return getLockSettings().verifyPassword(password, challenge, userId);
        } catch (RemoteException re) {
            return null;
        }
    }

    /**
     * Check to see if a password matches the saved password.  If no password exists,
     * always returns true.
+117 −32
Original line number Diff line number Diff line
@@ -387,6 +387,11 @@ public class LockSettingsService extends ILockSettings.Stub {
            throws RemoteException {
        byte[] currentHandle = getCurrentHandle(userId);

        if (pattern == null) {
            mStorage.writePatternHash(null, userId);
            return;
        }

        if (currentHandle == null) {
            if (savedCredential != null) {
                Slog.w(TAG, "Saved credential provided, but none stored");
@@ -406,9 +411,13 @@ public class LockSettingsService extends ILockSettings.Stub {
    @Override
    public void setLockPassword(String password, String savedCredential, int userId)
            throws RemoteException {

        byte[] currentHandle = getCurrentHandle(userId);

        if (password == null) {
            mStorage.writePasswordHash(null, userId);
            return;
        }

        if (currentHandle == null) {
            if (savedCredential != null) {
                Slog.w(TAG, "Saved credential provided, but none stored");
@@ -446,69 +455,143 @@ public class LockSettingsService extends ILockSettings.Stub {

    @Override
    public boolean checkPattern(String pattern, int userId) throws RemoteException {
        try {
            doVerifyPattern(pattern, false, 0, userId);
        } catch (VerificationFailedException ex) {
            return false;
        }

        return true;
    }

    @Override
    public byte[] verifyPattern(String pattern, long challenge, int userId)
            throws RemoteException {
        try {
            return doVerifyPattern(pattern, true, challenge, userId);
        } catch (VerificationFailedException ex) {
            return null;
        }
    }

    private byte[] doVerifyPattern(String pattern, boolean hasChallenge, long challenge,
            int userId) throws VerificationFailedException, RemoteException {
       checkPasswordReadPermission(userId);

       CredentialHash storedHash = mStorage.readPatternHash(userId);

        if (storedHash == null) {
            return true;
        if ((storedHash == null || storedHash.hash.length == 0) && TextUtils.isEmpty(pattern)) {
            // don't need to pass empty passwords to GateKeeper
            return null;
        }

        if (TextUtils.isEmpty(pattern)) {
            throw new VerificationFailedException();
        }

        if (storedHash.version == CredentialHash.VERSION_LEGACY) {
            // Try the old backend and upgrade if a match is found
            byte[] hash = LockPatternUtils.patternToHash(
                    LockPatternUtils.stringToPattern(pattern));
            boolean matched = Arrays.equals(hash, storedHash.hash);
            if (matched && !TextUtils.isEmpty(pattern)) {
            byte[] hash = mLockPatternUtils.patternToHash(
                    mLockPatternUtils.stringToPattern(pattern));
            if (Arrays.equals(hash, storedHash.hash)) {
                maybeUpdateKeystore(pattern, userId);
                // migrate pattern to GateKeeper
                // migrate password to GateKeeper
                setLockPattern(pattern, null, userId);
                if (!hasChallenge) {
                    return null;
                }
                // Fall through to get the auth token. Technically this should never happen,
                // as a user that had a legacy pattern would have to unlock their device
                // before getting to a flow with a challenge, but supporting for consistency.
            } else {
                throw new VerificationFailedException();
            }
        }

            return matched;
        byte[] token = null;
        if (hasChallenge) {
            token = getGateKeeperService()
                    .verifyChallenge(userId, challenge, storedHash.hash, pattern.getBytes());
            if (token == null) {
                throw new VerificationFailedException();
            }
        } else if (!getGateKeeperService().verify(userId, storedHash.hash, pattern.getBytes())) {
            throw new VerificationFailedException();
        }

        boolean matched = getGateKeeperService()
                .verify(userId, storedHash.hash, pattern.getBytes());
        if (matched && !TextUtils.isEmpty(pattern)) {
        // pattern has matched
        maybeUpdateKeystore(pattern, userId);
            return true;
        }
        return token;

        return matched;
    }

    @Override
    public boolean checkPassword(String password, int userId) throws RemoteException {
        try {
            doVerifyPassword(password, false, 0, userId);
        } catch (VerificationFailedException ex) {
            return false;
        }
        return true;
    }

    @Override
    public byte[] verifyPassword(String password, long challenge, int userId)
            throws RemoteException {
        try {
            return doVerifyPassword(password, true, challenge, userId);
        } catch (VerificationFailedException ex) {
            return null;
        }
    }

    private byte[] doVerifyPassword(String password, boolean hasChallenge, long challenge,
            int userId) throws VerificationFailedException, RemoteException {
       checkPasswordReadPermission(userId);

       CredentialHash storedHash = mStorage.readPasswordHash(userId);

        if (storedHash == null) {
            return true;
        if ((storedHash == null || storedHash.hash.length == 0) && TextUtils.isEmpty(password)) {
            // don't need to pass empty passwords to GateKeeper
            return null;
        }

        if (TextUtils.isEmpty(password)) {
            throw new VerificationFailedException();
        }

        if (storedHash.version == CredentialHash.VERSION_LEGACY) {
            byte[] hash = mLockPatternUtils.passwordToHash(password, userId);
            boolean matched = Arrays.equals(hash, storedHash.hash);
            if (matched && !TextUtils.isEmpty(password)) {
            if (Arrays.equals(hash, storedHash.hash)) {
                maybeUpdateKeystore(password, userId);
                // migrate password to GateKeeper
                setLockPassword(password, null, userId);
                if (!hasChallenge) {
                    return null;
                }
                // Fall through to get the auth token. Technically this should never happen,
                // as a user that had a legacy password would have to unlock their device
                // before getting to a flow with a challenge, but supporting for consistency.
            } else {
                throw new VerificationFailedException();
            }
            return matched;
        }

        byte[] token = null;
        if (hasChallenge) {
            token = getGateKeeperService()
                    .verifyChallenge(userId, challenge, storedHash.hash, password.getBytes());
            if (token == null) {
                throw new VerificationFailedException();
            }
        } else if (!getGateKeeperService().verify(userId, storedHash.hash, password.getBytes())) {
            throw new VerificationFailedException();
        }


        boolean matched = getGateKeeperService()
                .verify(userId, storedHash.hash, password.getBytes());
        if (!TextUtils.isEmpty(password) && matched) {
        // password has matched
        maybeUpdateKeystore(password, userId);
        return token;
    }

        return matched;
    }

    @Override
    public boolean checkVoldPassword(int userId) throws RemoteException {
@@ -624,4 +707,6 @@ public class LockSettingsService extends ILockSettings.Stub {
        return null;
    }

    private class VerificationFailedException extends Exception {}

}