Merge "Call KeyChain credential management app APIs Settings"

This commit is contained in:
Rubin Xu
2021-01-06 11:30:30 +00:00
committed by Android (Google) Code Review
2 changed files with 222 additions and 66 deletions

View File

@@ -19,17 +19,25 @@ package com.android.settings.security;
import android.annotation.Nullable; import android.annotation.Nullable;
import android.app.Activity; import android.app.Activity;
import android.app.admin.DevicePolicyManager; import android.app.admin.DevicePolicyManager;
import android.content.Context;
import android.content.pm.UserInfo;
import android.os.Bundle; import android.os.Bundle;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Process;
import android.os.RemoteException;
import android.os.UserManager;
import android.security.AppUriAuthenticationPolicy; import android.security.AppUriAuthenticationPolicy;
import android.security.Credentials; import android.security.Credentials;
import android.security.KeyChain; import android.security.KeyChain;
import android.text.TextUtils;
import android.util.Log; import android.util.Log;
import android.view.View; import android.view.View;
import android.widget.Button; import android.widget.Button;
import android.widget.LinearLayout; import android.widget.LinearLayout;
import android.widget.Toast;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;
import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView; import androidx.recyclerview.widget.RecyclerView;
@@ -66,29 +74,98 @@ public class RequestManageCredentials extends Activity {
private LinearLayout mButtonPanel; private LinearLayout mButtonPanel;
private ExtendedFloatingActionButton mExtendedFab; private ExtendedFloatingActionButton mExtendedFab;
private HandlerThread mKeyChainTread;
private KeyChain.KeyChainConnection mKeyChainConnection;
private boolean mDisplayingButtonPanel = false; private boolean mDisplayingButtonPanel = false;
@Override @Override
public void onCreate(@Nullable Bundle savedInstanceState) { public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
if (Credentials.ACTION_MANAGE_CREDENTIALS.equals(getIntent().getAction())) { if (!Credentials.ACTION_MANAGE_CREDENTIALS.equals(getIntent().getAction())) {
setContentView(R.layout.request_manage_credentials);
// This is not authenticated, as any app can ask to be the credential management app.
mCredentialManagerPackage = getReferrer().getHost();
mAuthenticationPolicy =
getIntent().getParcelableExtra(KeyChain.EXTRA_AUTHENTICATION_POLICY);
enforceValidAuthenticationPolicy(mAuthenticationPolicy);
loadRecyclerView();
loadButtons();
loadExtendedFloatingActionButton();
addOnScrollListener();
} else {
Log.e(TAG, "Unable to start activity because intent action is not " Log.e(TAG, "Unable to start activity because intent action is not "
+ Credentials.ACTION_MANAGE_CREDENTIALS); + Credentials.ACTION_MANAGE_CREDENTIALS);
finish(); finishWithResultCancelled();
return;
} }
if (isManagedDevice()) {
Log.e(TAG, "Credential management on managed devices should be done by the Device "
+ "Policy Controller, not a credential management app");
finishWithResultCancelled();
return;
}
mCredentialManagerPackage = getLaunchedFromPackage();
if (TextUtils.isEmpty(mCredentialManagerPackage)) {
Log.e(TAG, "Unknown credential manager app");
finishWithResultCancelled();
return;
}
setContentView(R.layout.request_manage_credentials);
mKeyChainTread = new HandlerThread("KeyChainConnection");
mKeyChainTread.start();
mKeyChainConnection = getKeyChainConnection(this, mKeyChainTread);
AppUriAuthenticationPolicy policy =
getIntent().getParcelableExtra(KeyChain.EXTRA_AUTHENTICATION_POLICY);
if (!isValidAuthenticationPolicy(policy)) {
Log.e(TAG, "Invalid authentication policy");
finishWithResultCancelled();
return;
}
mAuthenticationPolicy = policy;
loadRecyclerView();
loadButtons();
loadExtendedFloatingActionButton();
addOnScrollListener();
}
@Override
protected void onDestroy() {
super.onDestroy();
if (mKeyChainConnection != null) {
mKeyChainConnection.close();
mKeyChainConnection = null;
mKeyChainTread.quitSafely();
}
}
private boolean isValidAuthenticationPolicy(AppUriAuthenticationPolicy policy) {
if (policy == null || policy.getAppAndUriMappings().isEmpty()) {
return false;
}
try {
// Check whether any of the aliases in the policy already exist
for (String alias : policy.getAliases()) {
if (mKeyChainConnection.getService().requestPrivateKey(alias) != null) {
return false;
}
}
} catch (RemoteException e) {
Log.e(TAG, "Invalid authentication policy", e);
return false;
}
return true;
}
private boolean isManagedDevice() {
DevicePolicyManager dpm = getSystemService(DevicePolicyManager.class);
return dpm.getDeviceOwnerUser() != null
|| dpm.getProfileOwner() != null
|| hasManagedProfile();
}
private boolean hasManagedProfile() {
UserManager um = getSystemService(UserManager.class);
for (final UserInfo userInfo : um.getProfiles(getUserId())) {
if (userInfo.isManagedProfile()) {
return true;
}
}
return false;
} }
private void loadRecyclerView() { private void loadRecyclerView() {
@@ -107,8 +184,10 @@ public class RequestManageCredentials extends Activity {
Button dontAllowButton = findViewById(R.id.dont_allow_button); Button dontAllowButton = findViewById(R.id.dont_allow_button);
Button allowButton = findViewById(R.id.allow_button); Button allowButton = findViewById(R.id.allow_button);
dontAllowButton.setOnClickListener(finishRequestManageCredentials()); dontAllowButton.setOnClickListener(b -> {
allowButton.setOnClickListener(setCredentialManagementApp()); finishWithResultCancelled();
});
allowButton.setOnClickListener(b -> setOrUpdateCredentialManagementApp());
} }
private void loadExtendedFloatingActionButton() { private void loadExtendedFloatingActionButton() {
@@ -120,22 +199,26 @@ public class RequestManageCredentials extends Activity {
}); });
} }
private View.OnClickListener finishRequestManageCredentials() { private void setOrUpdateCredentialManagementApp() {
return v -> { try {
Toast.makeText(this, R.string.request_manage_credentials_dont_allow, mKeyChainConnection.getService().setCredentialManagementApp(
Toast.LENGTH_SHORT).show(); mCredentialManagerPackage, mAuthenticationPolicy);
setResult(RESULT_CANCELED); } catch (RemoteException e) {
finish(); Log.e(TAG, "Unable to set credential manager app", e);
}; }
finish();
} }
private View.OnClickListener setCredentialManagementApp() { @VisibleForTesting
return v -> { KeyChain.KeyChainConnection getKeyChainConnection(Context context, HandlerThread thread) {
// TODO: Implement allow logic final Handler handler = new Handler(thread.getLooper());
Toast.makeText(this, R.string.request_manage_credentials_allow, try {
Toast.LENGTH_SHORT).show(); KeyChain.KeyChainConnection connection = KeyChain.bindAsUser(
finish(); context, handler, Process.myUserHandle());
}; return connection;
} catch (InterruptedException e) {
throw new RuntimeException("Faile to bind to KeyChain", e);
}
} }
private void addOnScrollListener() { private void addOnScrollListener() {
@@ -182,12 +265,8 @@ public class RequestManageCredentials extends Activity {
< mRecyclerView.getAdapter().getItemCount() - 1; < mRecyclerView.getAdapter().getItemCount() - 1;
} }
private void enforceValidAuthenticationPolicy(AppUriAuthenticationPolicy policy) { private void finishWithResultCancelled() {
// TODO: Check whether any of the aliases in the policy already exist setResult(RESULT_CANCELED);
if (policy == null || policy.getAppAndUriMappings().isEmpty()) { finish();
Log.e(TAG, "Invalid authentication policy");
setResult(RESULT_CANCELED);
finish();
}
} }
} }

View File

@@ -18,18 +18,24 @@ package com.android.settings.security;
import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertThat;
import static org.robolectric.Shadows.shadowOf; import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.when;
import android.app.Activity; import android.app.admin.DevicePolicyManager;
import android.content.ComponentName;
import android.content.Intent; import android.content.Intent;
import android.net.Uri; import android.net.Uri;
import android.os.UserHandle;
import android.security.AppUriAuthenticationPolicy; import android.security.AppUriAuthenticationPolicy;
import android.security.Credentials; import android.security.Credentials;
import android.security.IKeyChainService;
import android.security.KeyChain; import android.security.KeyChain;
import android.widget.Button; import android.widget.Button;
import android.widget.LinearLayout; import android.widget.LinearLayout;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView; import androidx.recyclerview.widget.RecyclerView;
import com.android.settings.R; import com.android.settings.R;
@@ -37,10 +43,10 @@ import com.android.settings.R;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations; import org.mockito.MockitoAnnotations;
import org.robolectric.Robolectric; import org.robolectric.Robolectric;
import org.robolectric.RobolectricTestRunner; import org.robolectric.RobolectricTestRunner;
import org.robolectric.shadows.ShadowActivity;
@RunWith(RobolectricTestRunner.class) @RunWith(RobolectricTestRunner.class)
public class RequestManageCredentialsTest { public class RequestManageCredentialsTest {
@@ -52,7 +58,14 @@ public class RequestManageCredentialsTest {
private RequestManageCredentials mActivity; private RequestManageCredentials mActivity;
private ShadowActivity mShadowActivity; @Mock
private DevicePolicyManager mDevicePolicyManager;
@Mock
private KeyChain.KeyChainConnection mKeyChainConnection;
@Mock
private IKeyChainService mKeyChainService;
@Before @Before
public void setUp() { public void setUp() {
@@ -60,21 +73,98 @@ public class RequestManageCredentialsTest {
} }
@Test @Test
public void onCreate_intentActionNotManageCredentials_finishActivity() { public void onCreate_intentActionNotManageCredentials_finishActivity()
final Intent intent = new Intent("android.security.ANOTHER_ACTION"); throws Exception {
final Intent intent = new Intent(Credentials.ACTION_MANAGE_CREDENTIALS + "_bad");
intent.putExtra(KeyChain.EXTRA_AUTHENTICATION_POLICY, AUTHENTICATION_POLICY);
setupActivityWithAction(intent);
when(mDevicePolicyManager.getDeviceOwnerUser()).thenReturn(null);
when(mDevicePolicyManager.getProfileOwner()).thenReturn(null);
when(mActivity.getLaunchedFromPackage()).thenReturn("com.example.credapp");
initActivity(intent); mActivity.onCreate(null);
assertThat(mActivity).isNotNull(); assertThat(mActivity).isNotNull();
assertThat(mActivity.isFinishing()).isTrue(); assertThat(mActivity.isFinishing()).isTrue();
} }
@Test @Test
public void onCreate_authenticationPolicyProvided_startActivity() { public void onCreate_noAuthenticationPolicy_finishActivity()
throws Exception {
final Intent intent = new Intent(Credentials.ACTION_MANAGE_CREDENTIALS);
setupActivityWithAction(intent);
when(mDevicePolicyManager.getDeviceOwnerUser()).thenReturn(null);
when(mDevicePolicyManager.getProfileOwner()).thenReturn(null);
when(mActivity.getLaunchedFromPackage()).thenReturn("com.example.credapp");
mActivity.onCreate(null);
assertThat(mActivity).isNotNull();
assertThat(mActivity.isFinishing()).isTrue();
}
@Test
public void onCreate_invalidAuthenticationPolicy_finishActivity()
throws Exception {
final Intent intent = new Intent(Credentials.ACTION_MANAGE_CREDENTIALS);
intent.putExtra(KeyChain.EXTRA_AUTHENTICATION_POLICY, "Invalid policy");
setupActivityWithAction(intent);
when(mDevicePolicyManager.getDeviceOwnerUser()).thenReturn(null);
when(mDevicePolicyManager.getProfileOwner()).thenReturn(null);
when(mActivity.getLaunchedFromPackage()).thenReturn("com.example.credapp");
mActivity.onCreate(null);
assertThat(mActivity).isNotNull();
assertThat(mActivity.isFinishing()).isTrue();
}
@Test
public void onCreate_runOnManagedProfile_finishActivity()
throws Exception {
final Intent intent = new Intent(Credentials.ACTION_MANAGE_CREDENTIALS); final Intent intent = new Intent(Credentials.ACTION_MANAGE_CREDENTIALS);
intent.putExtra(KeyChain.EXTRA_AUTHENTICATION_POLICY, AUTHENTICATION_POLICY); intent.putExtra(KeyChain.EXTRA_AUTHENTICATION_POLICY, AUTHENTICATION_POLICY);
initActivity(intent); setupActivityWithAction(intent);
when(mDevicePolicyManager.getDeviceOwnerUser()).thenReturn(null);
when(mDevicePolicyManager.getProfileOwner()).thenReturn(new ComponentName("pkg", "cls"));
when(mActivity.getLaunchedFromPackage()).thenReturn("com.example.credapp");
mActivity.onCreate(null);
assertThat(mActivity).isNotNull();
assertThat(mActivity.isFinishing()).isTrue();
}
@Test
public void onCreate_runOnManagedDevice_finishActivity()
throws Exception {
final Intent intent = new Intent(Credentials.ACTION_MANAGE_CREDENTIALS);
intent.putExtra(KeyChain.EXTRA_AUTHENTICATION_POLICY, AUTHENTICATION_POLICY);
setupActivityWithAction(intent);
when(mDevicePolicyManager.getDeviceOwnerUser()).thenReturn(UserHandle.SYSTEM);
when(mDevicePolicyManager.getProfileOwner()).thenReturn(null);
when(mActivity.getLaunchedFromPackage()).thenReturn("com.example.credapp");
mActivity.onCreate(null);
assertThat(mActivity).isNotNull();
assertThat(mActivity.isFinishing()).isTrue();
}
@Test
public void onCreate_authenticationPolicyProvided_startActivity() throws Exception {
final Intent intent = new Intent(Credentials.ACTION_MANAGE_CREDENTIALS);
intent.putExtra(KeyChain.EXTRA_AUTHENTICATION_POLICY, AUTHENTICATION_POLICY);
setupActivityWithAction(intent);
when(mDevicePolicyManager.getDeviceOwnerUser()).thenReturn(null);
when(mDevicePolicyManager.getProfileOwner()).thenReturn(null);
when(mActivity.getLaunchedFromPackage()).thenReturn("com.example.credapp");
mActivity.onCreate(null);
assertThat(mActivity).isNotNull(); assertThat(mActivity).isNotNull();
assertThat(mActivity.isFinishing()).isFalse(); assertThat(mActivity.isFinishing()).isFalse();
@@ -84,23 +174,10 @@ public class RequestManageCredentialsTest {
assertThat((Button) mActivity.findViewById(R.id.dont_allow_button)).isNotNull(); assertThat((Button) mActivity.findViewById(R.id.dont_allow_button)).isNotNull();
} }
@Test private void setupActivityWithAction(Intent intent) throws Exception {
public void onCreate_dontAllowButtonClicked_finishActivity() { mActivity = spy(Robolectric.buildActivity(RequestManageCredentials.class, intent).get());
final Intent intent = new Intent(Credentials.ACTION_MANAGE_CREDENTIALS); doReturn(mKeyChainConnection).when(mActivity).getKeyChainConnection(eq(mActivity), any());
intent.putExtra(KeyChain.EXTRA_AUTHENTICATION_POLICY, AUTHENTICATION_POLICY); doReturn(mDevicePolicyManager).when(mActivity).getSystemService(DevicePolicyManager.class);
when(mKeyChainConnection.getService()).thenReturn(mKeyChainService);
initActivity(intent);
Button dontAllowButton = mActivity.findViewById(R.id.dont_allow_button);
assertThat(dontAllowButton.hasOnClickListeners()).isTrue();
dontAllowButton.performClick();
assertThat(mActivity.isFinishing()).isTrue();
assertThat(mShadowActivity.getResultCode()).isEqualTo(Activity.RESULT_CANCELED);
} }
private void initActivity(@NonNull Intent intent) {
mActivity = Robolectric.buildActivity(RequestManageCredentials.class, intent).setup().get();
mShadowActivity = shadowOf(mActivity);
}
} }