1/*
2 * Copyright (C) 2018 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16package com.android.server.locksettings;
17
18import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC;
19import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED;
20
21import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PASSWORD;
22import static com.android.server.testutils.TestUtils.assertExpectException;
23
24import static org.mockito.Mockito.anyInt;
25import static org.mockito.Mockito.atLeastOnce;
26import static org.mockito.Mockito.when;
27import static org.mockito.Mockito.verify;
28
29import android.os.RemoteException;
30
31import com.android.internal.widget.LockPatternUtils;
32import com.android.internal.widget.VerifyCredentialResponse;
33import com.android.server.locksettings.SyntheticPasswordManager.AuthenticationResult;
34
35import java.util.ArrayList;
36
37import org.mockito.ArgumentCaptor;
38
39/**
40 * Run the synthetic password tests with caching enabled.
41 *
42 * By default, those tests run without caching. Untrusted credential reset depends on caching so
43 * this class included those tests.
44 */
45public class CachedSyntheticPasswordTests extends SyntheticPasswordTests {
46
47    @Override
48    protected void setUp() throws Exception {
49        super.setUp();
50        enableSpCaching(true);
51    }
52
53    private void enableSpCaching(boolean enable) {
54        when(mDevicePolicyManagerInternal
55                .canUserHaveUntrustedCredentialReset(anyInt())).thenReturn(enable);
56    }
57
58    public void testSyntheticPasswordClearCredentialUntrusted() throws RemoteException {
59        final String PASSWORD = "testSyntheticPasswordClearCredential-password";
60        final String NEWPASSWORD = "testSyntheticPasswordClearCredential-newpassword";
61
62        initializeCredentialUnderSP(PASSWORD, PRIMARY_USER_ID);
63        long sid = mGateKeeperService.getSecureUserId(PRIMARY_USER_ID);
64        // clear password
65        mService.setLockCredential(null, LockPatternUtils.CREDENTIAL_TYPE_NONE, null,
66                PASSWORD_QUALITY_UNSPECIFIED, PRIMARY_USER_ID);
67        assertEquals(0, mGateKeeperService.getSecureUserId(PRIMARY_USER_ID));
68
69        // set a new password
70        mService.setLockCredential(NEWPASSWORD, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, null,
71                PASSWORD_QUALITY_ALPHABETIC, PRIMARY_USER_ID);
72        assertEquals(VerifyCredentialResponse.RESPONSE_OK, mService.verifyCredential(
73                NEWPASSWORD, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0, PRIMARY_USER_ID)
74                    .getResponseCode());
75        assertNotEquals(sid, mGateKeeperService.getSecureUserId(PRIMARY_USER_ID));
76    }
77
78    public void testSyntheticPasswordChangeCredentialUntrusted() throws RemoteException {
79        final String PASSWORD = "testSyntheticPasswordClearCredential-password";
80        final String NEWPASSWORD = "testSyntheticPasswordClearCredential-newpassword";
81
82        initializeCredentialUnderSP(PASSWORD, PRIMARY_USER_ID);
83        long sid = mGateKeeperService.getSecureUserId(PRIMARY_USER_ID);
84        // Untrusted change password
85        mService.setLockCredential(NEWPASSWORD, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, null,
86                PASSWORD_QUALITY_ALPHABETIC, PRIMARY_USER_ID);
87        assertNotEquals(0, mGateKeeperService.getSecureUserId(PRIMARY_USER_ID));
88        assertNotEquals(sid, mGateKeeperService.getSecureUserId(PRIMARY_USER_ID));
89
90        // Verify the password
91        assertEquals(VerifyCredentialResponse.RESPONSE_OK, mService.verifyCredential(
92                NEWPASSWORD, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0, PRIMARY_USER_ID)
93                    .getResponseCode());
94    }
95
96    public void testUntrustedCredentialChangeMaintainsAuthSecret() throws RemoteException {
97        final String PASSWORD = "testUntrustedCredentialChangeMaintainsAuthSecret-password";
98        final String NEWPASSWORD = "testUntrustedCredentialChangeMaintainsAuthSecret-newpassword";
99
100        initializeCredentialUnderSP(PASSWORD, PRIMARY_USER_ID);
101        // Untrusted change password
102        mService.setLockCredential(NEWPASSWORD, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, null,
103                PASSWORD_QUALITY_ALPHABETIC, PRIMARY_USER_ID);
104
105        // Verify the password
106        assertEquals(VerifyCredentialResponse.RESPONSE_OK, mService.verifyCredential(
107                NEWPASSWORD, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0, PRIMARY_USER_ID)
108                    .getResponseCode());
109
110        // Ensure the same secret was passed each time
111        ArgumentCaptor<ArrayList<Byte>> secret = ArgumentCaptor.forClass(ArrayList.class);
112        verify(mAuthSecretService, atLeastOnce()).primaryUserCredential(secret.capture());
113        assertEquals(1, secret.getAllValues().stream().distinct().count());
114    }
115
116    public void testUntrustedCredentialChangeBlockedIfSpNotCached() throws RemoteException {
117        final String PASSWORD = "testUntrustedCredentialChangeBlockedIfSpNotCached-password";
118        final String NEWPASSWORD = "testUntrustedCredentialChangeBlockedIfSpNotCached-newpassword";
119
120        // Disable caching for this test
121        enableSpCaching(false);
122
123        initializeCredentialUnderSP(PASSWORD, PRIMARY_USER_ID);
124        long sid = mGateKeeperService.getSecureUserId(PRIMARY_USER_ID);
125        // Untrusted change password
126        assertExpectException(IllegalStateException.class, /* messageRegex= */ null,
127                () -> mService.setLockCredential(
128                        NEWPASSWORD, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD,
129                        null, PASSWORD_QUALITY_ALPHABETIC, PRIMARY_USER_ID));
130        assertEquals(sid, mGateKeeperService.getSecureUserId(PRIMARY_USER_ID));
131
132        // Verify the new password doesn't work but the old one still does
133        assertEquals(VerifyCredentialResponse.RESPONSE_ERROR, mService.verifyCredential(
134                NEWPASSWORD, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0, PRIMARY_USER_ID)
135                        .getResponseCode());
136        assertEquals(VerifyCredentialResponse.RESPONSE_OK, mService.verifyCredential(
137                PASSWORD, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0, PRIMARY_USER_ID)
138                        .getResponseCode());
139    }
140
141}
142