blob: 4bedd527e54da025bd1a3ea6ef43a4d19e1f3bf7 [file] [log] [blame]
/*
* Copyright (C) 2021 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 android.devicepolicy.cts;
import static com.android.bedstead.remotedpc.RemoteDpc.DPC_COMPONENT_NAME;
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertThrows;
import android.content.Context;
import android.net.Uri;
import android.os.Handler;
import android.os.Looper;
import android.security.KeyChain;
import android.security.KeyChainException;
import com.android.activitycontext.ActivityContext;
import com.android.bedstead.harrier.BedsteadJUnit4;
import com.android.bedstead.harrier.DeviceState;
import com.android.bedstead.harrier.annotations.Postsubmit;
import com.android.bedstead.harrier.annotations.enterprise.CanSetPolicyTest;
import com.android.bedstead.harrier.annotations.enterprise.PositivePolicyTest;
import com.android.bedstead.harrier.policies.KeyManagement;
import com.android.compatibility.common.util.BlockingCallback;
import com.android.compatibility.common.util.FakeKeys;
import org.junit.ClassRule;
import org.junit.Ignore;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.io.ByteArrayInputStream;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* Test that a DPC can manage keys and certificate on a device by installing, generating and
* removing key pairs via DevicePolicyManager APIs. The instrumented test app can use the installed
* keys by requesting access to them and retrieving them via KeyChain APIs.
*/
@RunWith(BedsteadJUnit4.class)
public class KeyManagementTest {
private static final String RSA = "RSA";
private static final String RSA_ALIAS = "com.android.test.valid-rsa-key-1";
private static final PrivateKey PRIVATE_KEY =
generatePrivateKey(FakeKeys.FAKE_RSA_1.privateKey, RSA);
private static final Certificate CERTIFICATE =
generateCertificate(FakeKeys.FAKE_RSA_1.caCertificate);
private static final Certificate[] CERTIFICATES = new Certificate[]{CERTIFICATE};
private final ExecutorService mExecutor = Executors.newSingleThreadExecutor();
private final Handler mHandler = new Handler(Looper.getMainLooper());
@ClassRule
@Rule
public static final DeviceState sDeviceState = new DeviceState();
@Test
@Postsubmit(reason = "new test")
@CanSetPolicyTest(policy = KeyManagement.class)
@Ignore("b/200112753")
public void installKeyPair_validRsaKeyPair_success() throws Exception {
try {
// Install keypair
assertThat(sDeviceState.dpc().devicePolicyManager()
.installKeyPair(DPC_COMPONENT_NAME, PRIVATE_KEY, CERTIFICATE,
RSA_ALIAS)).isTrue();
} finally {
// Remove keypair
sDeviceState.dpc().devicePolicyManager().removeKeyPair(DPC_COMPONENT_NAME, RSA_ALIAS);
}
}
@Test
@Postsubmit(reason = "new test")
@CanSetPolicyTest(policy = KeyManagement.class, singleTestOnly = true)
public void installKeyPair_nullPrivateKey_throwException() throws Exception {
assertThrows(NullPointerException.class,
() -> sDeviceState.dpc().devicePolicyManager().installKeyPair(
DPC_COMPONENT_NAME, /* privKey = */ null, CERTIFICATE, RSA_ALIAS));
}
@Test
@Postsubmit(reason = "new test")
@CanSetPolicyTest(policy = KeyManagement.class, singleTestOnly = true)
public void installKeyPair_nullCertificate_throwException() throws Exception {
assertThrows(NullPointerException.class,
() -> sDeviceState.dpc().devicePolicyManager().installKeyPair(
DPC_COMPONENT_NAME, PRIVATE_KEY, /* cert = */ null, RSA_ALIAS));
}
@Test
@Postsubmit(reason = "new test")
@CanSetPolicyTest(policy = KeyManagement.class, singleTestOnly = true)
@Ignore("b/200112753")
public void installKeyPair_nullAdminComponent_throwException() throws Exception {
assertThrows(SecurityException.class,
() -> sDeviceState.dpc().devicePolicyManager().installKeyPair(
/* admin = */ null, PRIVATE_KEY, CERTIFICATE, RSA_ALIAS));
}
@Test
@Ignore
@Postsubmit(reason = "new test")
@PositivePolicyTest(policy = KeyManagement.class)
public void installKeyPair_withAutomatedAccess_aliasIsGranted() throws Exception {
try {
// Install keypair with automated access
sDeviceState.dpc().devicePolicyManager().installKeyPair(DPC_COMPONENT_NAME, PRIVATE_KEY,
CERTIFICATES, RSA_ALIAS, /* requestAccess = */ true);
// TODO(b/198407955): Call KeyChain.getPrivateKey with the DPC context and verify
// the private key is not null
} finally {
// Remove keypair
sDeviceState.dpc().devicePolicyManager().removeKeyPair(DPC_COMPONENT_NAME, RSA_ALIAS);
}
}
@Test
@Ignore
@Postsubmit(reason = "new test")
@PositivePolicyTest(policy = KeyManagement.class)
public void installKeyPair_withoutAutomatedAccess_aliasIsNotGranted() throws Exception {
try {
// Install keypair with automated access
sDeviceState.dpc().devicePolicyManager().installKeyPair(DPC_COMPONENT_NAME, PRIVATE_KEY,
CERTIFICATES, RSA_ALIAS, /* requestAccess = */ false);
// TODO(b/198407955): Call KeyChain.getPrivateKey with the DPC context and verify
// the private key is null
} finally {
// Remove keypair
sDeviceState.dpc().devicePolicyManager().removeKeyPair(DPC_COMPONENT_NAME, RSA_ALIAS);
}
}
@Test
@Postsubmit(reason = "new test")
@CanSetPolicyTest(policy = KeyManagement.class)
@Ignore("b/200112753")
public void removeKeyPair_validRsaKeyPair_success() throws Exception {
try {
// Install keypair
sDeviceState.dpc().devicePolicyManager()
.installKeyPair(DPC_COMPONENT_NAME, PRIVATE_KEY, CERTIFICATE, RSA_ALIAS);
} finally {
// Remove keypair
assertThat(sDeviceState.dpc().devicePolicyManager()
.removeKeyPair(DPC_COMPONENT_NAME, RSA_ALIAS)).isTrue();
}
}
@Test
@Postsubmit(reason = "new test")
@CanSetPolicyTest(policy = KeyManagement.class)
public void hasKeyPair_nonExistentAlias_false() throws Exception {
assertThat(sDeviceState.dpc().devicePolicyManager().hasKeyPair(RSA_ALIAS)).isFalse();
}
@Test
@Postsubmit(reason = "new test")
@CanSetPolicyTest(policy = KeyManagement.class)
@Ignore("b/200112753")
public void hasKeyPair_installedAlias_true() throws Exception {
try {
// Install keypair
sDeviceState.dpc().devicePolicyManager()
.installKeyPair(DPC_COMPONENT_NAME, PRIVATE_KEY, CERTIFICATE, RSA_ALIAS);
assertThat(sDeviceState.dpc().devicePolicyManager().hasKeyPair(RSA_ALIAS)).isTrue();
} finally {
// Remove keypair
sDeviceState.dpc().devicePolicyManager().removeKeyPair(DPC_COMPONENT_NAME, RSA_ALIAS);
}
}
@Test
@Postsubmit(reason = "new test")
@CanSetPolicyTest(policy = KeyManagement.class)
@Ignore("b/200112753")
public void hasKeyPair_removedAlias_false() {
try {
// Install keypair
sDeviceState.dpc().devicePolicyManager()
.installKeyPair(DPC_COMPONENT_NAME, PRIVATE_KEY, CERTIFICATE, RSA_ALIAS);
sDeviceState.dpc().devicePolicyManager().removeKeyPair(DPC_COMPONENT_NAME, RSA_ALIAS);
assertThat(sDeviceState.dpc().devicePolicyManager().hasKeyPair(RSA_ALIAS)).isFalse();
} finally {
// Remove keypair
sDeviceState.dpc().devicePolicyManager().removeKeyPair(DPC_COMPONENT_NAME, RSA_ALIAS);
}
}
@Test
@Postsubmit(reason = "new test")
@PositivePolicyTest(policy = KeyManagement.class)
@Ignore("b/200112753")
public void choosePrivateKeyAlias_aliasIsSelectedByAdmin_returnAlias() throws Exception {
try {
// Install keypair
sDeviceState.dpc().devicePolicyManager()
.installKeyPair(DPC_COMPONENT_NAME, PRIVATE_KEY, CERTIFICATE, RSA_ALIAS);
KeyChainAliasCallback callback = new KeyChainAliasCallback();
choosePrivateKeyAlias(callback, RSA_ALIAS);
assertThat(callback.await()).isEqualTo(RSA_ALIAS);
} finally {
// Remove keypair
sDeviceState.dpc().devicePolicyManager().removeKeyPair(DPC_COMPONENT_NAME, RSA_ALIAS);
}
}
@Test
@Postsubmit(reason = "new test")
@PositivePolicyTest(policy = KeyManagement.class)
@Ignore("b/200112753")
public void choosePrivateKeyAlias_nonUserSelectedAliasIsSelectedByAdmin_returnAlias()
throws Exception {
try {
// Install keypair which is not user selectable
sDeviceState.dpc().devicePolicyManager().installKeyPair(DPC_COMPONENT_NAME, PRIVATE_KEY,
CERTIFICATES, RSA_ALIAS, /* flags = */ 0);
KeyChainAliasCallback callback = new KeyChainAliasCallback();
choosePrivateKeyAlias(callback, RSA_ALIAS);
assertThat(callback.await()).isEqualTo(RSA_ALIAS);
} finally {
// Remove keypair
sDeviceState.dpc().devicePolicyManager().removeKeyPair(DPC_COMPONENT_NAME, RSA_ALIAS);
}
}
@Test
@Postsubmit(reason = "new test")
@PositivePolicyTest(policy = KeyManagement.class)
@Ignore("b/200112753")
public void getPrivateKey_aliasIsGranted_returnPrivateKey() throws Exception {
try {
// Install keypair
sDeviceState.dpc().devicePolicyManager()
.installKeyPair(DPC_COMPONENT_NAME, PRIVATE_KEY, CERTIFICATE, RSA_ALIAS);
// Grant alias via {@code KeyChain.choosePrivateKeyAlias}
KeyChainAliasCallback callback = new KeyChainAliasCallback();
choosePrivateKeyAlias(callback, RSA_ALIAS);
callback.await();
// TODO(b/198297904): Allow runWithContext to run off the main thread
ActivityContext.runWithContext((activity) -> mExecutor.execute(() -> {
// Get private key for the granted alias
final PrivateKey privateKey = getPrivateKey(activity, RSA_ALIAS);
mHandler.post(() -> {
assertThat(privateKey).isNotNull();
assertThat(privateKey.getAlgorithm()).isEqualTo(RSA);
});
}));
} finally {
// Remove keypair
sDeviceState.dpc().devicePolicyManager().removeKeyPair(DPC_COMPONENT_NAME, RSA_ALIAS);
}
}
private static class KeyChainAliasCallback extends BlockingCallback<String> implements
android.security.KeyChainAliasCallback {
@Override
public void alias(final String chosenAlias) {
callbackTriggered(chosenAlias);
}
}
private static Uri getUri(String alias) {
try {
return Uri.parse("https://example.org/?alias=" + URLEncoder.encode(alias, "UTF-8"));
} catch (UnsupportedEncodingException e) {
throw new AssertionError("Unable to parse URI." + e);
}
}
private static void choosePrivateKeyAlias(KeyChainAliasCallback callback, String alias) {
/* Pass the alias as a GET to an imaginary server instead of explicitly asking for it,
* to make sure the DPC actually has to do some work to grant the cert.
*/
try {
ActivityContext.runWithContext(
(activity) -> KeyChain.choosePrivateKeyAlias(activity, callback, /* keyTypes= */
null, /* issuers= */ null, getUri(alias), /* alias = */ null)
);
} catch (InterruptedException e) {
throw new AssertionError("Unable to choose private key alias." + e);
}
}
private static PrivateKey getPrivateKey(Context context, String alias) {
try {
return KeyChain.getPrivateKey(context, alias);
} catch (KeyChainException | InterruptedException e) {
throw new AssertionError("Failed to get private key." + e);
}
}
private static PrivateKey generatePrivateKey(final byte[] key, String type) {
try {
return KeyFactory.getInstance(type).generatePrivate(
new PKCS8EncodedKeySpec(key));
} catch (InvalidKeySpecException | NoSuchAlgorithmException e) {
throw new AssertionError("Unable to get private key." + e);
}
}
private static Certificate generateCertificate(byte[] cert) {
try {
return CertificateFactory.getInstance("X.509").generateCertificate(
new ByteArrayInputStream(cert));
} catch (CertificateException e) {
throw new AssertionError("Unable to get certificate." + e);
}
}
}