CTS tests for delegated cert installer API
Include hostside tests and negative API tests in DevicePolicyManagerTest
Bug: 19550928
Change-Id: Id59a6aa96ff31a3e9c78b195885f88cb69fc16ca
diff --git a/CtsTestCaseList.mk b/CtsTestCaseList.mk
index 21b4027..aa3edaa 100644
--- a/CtsTestCaseList.mk
+++ b/CtsTestCaseList.mk
@@ -67,6 +67,7 @@
cts_support_packages := \
CtsAccelerationTestStubs \
CtsAppTestStubs \
+ CtsCertInstallerApp \
CtsDeviceAdmin \
CtsDeviceOpenGl \
CtsDeviceOwnerApp \
diff --git a/hostsidetests/devicepolicy/app/CertInstaller/Android.mk b/hostsidetests/devicepolicy/app/CertInstaller/Android.mk
new file mode 100644
index 0000000..22a78e2
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/CertInstaller/Android.mk
@@ -0,0 +1,31 @@
+# Copyright (C) 2015 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.
+
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_PACKAGE_NAME := CtsCertInstallerApp
+
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_JAVA_LIBRARIES := android.test.runner
+
+LOCAL_SDK_VERSION := current
+
+include $(BUILD_CTS_PACKAGE)
diff --git a/hostsidetests/devicepolicy/app/CertInstaller/AndroidManifest.xml b/hostsidetests/devicepolicy/app/CertInstaller/AndroidManifest.xml
new file mode 100644
index 0000000..c50e5fd
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/CertInstaller/AndroidManifest.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2015 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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.cts.certinstaller">
+
+ <uses-sdk android:minSdkVersion="22"/>
+
+ <application>
+ <activity android:name="CertInstallerActivity">
+ <intent-filter>
+ <action android:name="com.android.cts.certinstaller.install_cert" />
+ <action android:name="com.android.cts.certinstaller.remove_cert" />
+ <action android:name="com.android.cts.certinstaller.verify_cert" />
+ <action android:name="com.android.cts.certinstaller.install_keypair" />
+ <category android:name="android.intent.category.DEFAULT" />
+ </intent-filter>
+ </activity>
+ </application>
+
+</manifest>
diff --git a/hostsidetests/devicepolicy/app/CertInstaller/src/com/android/cts/certinstaller/CertInstallerActivity.java b/hostsidetests/devicepolicy/app/CertInstaller/src/com/android/cts/certinstaller/CertInstallerActivity.java
new file mode 100644
index 0000000..b14822b
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/CertInstaller/src/com/android/cts/certinstaller/CertInstallerActivity.java
@@ -0,0 +1,177 @@
+/*
+ * Copyright (C) 2015 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.cts.certinstaller;
+
+import android.app.Activity;
+import android.app.admin.DevicePolicyManager;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import android.util.Base64;
+import android.util.Base64InputStream;
+import android.util.Log;
+
+import java.io.ByteArrayInputStream;
+import java.security.cert.CertificateException;
+import java.security.cert.CertificateFactory;
+import java.security.spec.PKCS8EncodedKeySpec;
+import java.security.KeyFactory;
+import java.security.PrivateKey;
+import java.security.cert.Certificate;
+import java.util.List;
+
+/**
+ * Delegated certificate installer app that responds to specific intents and executes various DPM
+ * certificate manipulation APIs. The following APIs are exercised:
+ * {@link DevicePolicyManager#installCaCert},
+ * {@link DevicePolicyManager#uninstallCaCert},
+ * {@link DevicePolicyManager#hasCaCertInstalled},
+ * {@link DevicePolicyManager#getInstalledCaCerts},
+ * {@link DevicePolicyManager#installKeyPair}.
+ */
+public class CertInstallerActivity extends Activity {
+
+ private static final String TAG = "DelegatedCertInstaller";
+ // exercises {@link DevicePolicyManager#installCaCert} and
+ // {@link DevicePolicyManager#hasCaCertInstalled},
+ private static final String ACTION_INSTALL_CERT = "com.android.cts.certinstaller.install_cert";
+ // exercises {@link DevicePolicyManager#uninstallCaCert} and
+ // {@link DevicePolicyManager#hasCaCertInstalled},
+ private static final String ACTION_REMOVE_CERT = "com.android.cts.certinstaller.remove_cert";
+ // exercises {@link DevicePolicyManager#getInstalledCaCerts},
+ private static final String ACTION_VERIFY_CERT = "com.android.cts.certinstaller.verify_cert";
+ // exercises {@link DevicePolicyManager#installKeyPair},
+ private static final String ACTION_INSTALL_KEYPAIR =
+ "com.android.cts.certinstaller.install_keypair";
+
+ private static final String ACTION_CERT_OPERATION_DONE = "com.android.cts.certinstaller.done";
+
+ private static final String EXTRA_CERT_DATA = "extra_cert_data";
+ private static final String EXTRA_KEY_DATA = "extra_key_data";
+ private static final String EXTRA_KEY_ALIAS = "extra_key_alias";
+ private static final String EXTRA_RESULT_VALUE = "extra_result_value";
+ private static final String EXTRA_RESULT_EXCEPTION = "extra_result_exception";
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ Intent intent = getIntent();
+ if (intent == null) {
+ finish();
+ return;
+ }
+
+ String action = intent.getAction();
+ DevicePolicyManager dpm = (DevicePolicyManager) getSystemService(
+ Context.DEVICE_POLICY_SERVICE);
+
+ byte[] certBuffer;
+
+ if (ACTION_INSTALL_CERT.equals(action)) {
+ try {
+ certBuffer = intent.getByteArrayExtra(EXTRA_CERT_DATA);
+ // Verify cert is not currently installed.
+ if (dpm.hasCaCertInstalled(null, certBuffer)) {
+ throw new RuntimeException("Cert already on device?");
+ }
+ if (!dpm.installCaCert(null, certBuffer)) {
+ throw new RuntimeException("installCaCert returned false.");
+ }
+ if (!dpm.hasCaCertInstalled(null, certBuffer)) {
+ throw new RuntimeException("Cannot find cert after installation.");
+ }
+ sendResult(true, null);
+ } catch (Exception e) {
+ Log.e(TAG, "Exception raised duing ACTION_INSTALL_CERT", e);
+ sendResult(false, e);
+ }
+ } else if (ACTION_REMOVE_CERT.equals(action)) {
+ try {
+ certBuffer = intent.getByteArrayExtra(EXTRA_CERT_DATA);
+ if (!dpm.hasCaCertInstalled(null, certBuffer)) {
+ throw new RuntimeException("Trying to uninstall a non-existent cert.");
+ }
+ dpm.uninstallCaCert(null, certBuffer);
+ sendResult(!dpm.hasCaCertInstalled(null, certBuffer), null);
+ } catch (Exception e) {
+ Log.e(TAG, "Exception raised duing ACTION_REMOVE_CERT", e);
+ sendResult(false, e);
+ }
+ } else if (ACTION_VERIFY_CERT.equals(action)) {
+ try {
+ certBuffer = intent.getByteArrayExtra(EXTRA_CERT_DATA);
+ sendResult(containsCertificate(dpm.getInstalledCaCerts(null), certBuffer), null);
+ } catch (Exception e) {
+ Log.e(TAG, "Exception raised duing ACTION_VERIFY_CERT", e);
+ sendResult(false, e);
+ }
+ } else if (ACTION_INSTALL_KEYPAIR.equals(action)) {
+ String alias = intent.getStringExtra(EXTRA_KEY_ALIAS);
+ String key = intent.getStringExtra(EXTRA_KEY_DATA);
+ String cert = intent.getStringExtra(EXTRA_CERT_DATA);
+ try {
+ PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(
+ Base64.decode(key, Base64.DEFAULT));
+ KeyFactory kf = KeyFactory.getInstance("RSA");
+ PrivateKey privatekey = kf.generatePrivate(keySpec);
+
+ Certificate certificate = CertificateFactory.getInstance("X.509")
+ .generateCertificate(
+ new Base64InputStream(new ByteArrayInputStream(cert.getBytes()),
+ Base64.DEFAULT));
+ // Unfortunately there is no programmatically way to check if the given private key
+ // is indeed in the key store as a unprivileged app. So we just rely on
+ // installKeyPair() returning true as the success criteria of this test. Separate
+ // CTS keychain tests will make sure the API's behaviour is correct.
+ // Note: installKeyPair() will silently return false if there is no lockscreen
+ // password, however the test setup should have set one already.
+ sendResult(dpm.installKeyPair(null, privatekey, certificate, alias), null);
+ } catch (Exception e) {
+ Log.e(TAG, "Exception raised duing ACTION_INSTALL_KEYPAIR", e);
+ sendResult(false, e);
+ }
+ }
+ finish();
+ }
+
+ private void sendResult(boolean succeed, Exception e) {
+ Intent intent = new Intent();
+ intent.setAction(ACTION_CERT_OPERATION_DONE);
+ intent.putExtra(EXTRA_RESULT_VALUE, succeed);
+ if (e != null) {
+ intent.putExtra(EXTRA_RESULT_EXCEPTION, e);
+ }
+ sendBroadcast(intent);
+ }
+
+ private boolean containsCertificate(List<byte[]> certificates, byte[] toMatch)
+ throws CertificateException {
+ Certificate certificateToMatch = readCertificate(toMatch);
+ for (byte[] certBuffer : certificates) {
+ Certificate cert = readCertificate(certBuffer);
+ if (certificateToMatch.equals(cert)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private Certificate readCertificate(byte[] certBuffer) throws CertificateException {
+ final CertificateFactory certFactory = CertificateFactory.getInstance("X.509");
+ return certFactory.generateCertificate(new ByteArrayInputStream(certBuffer));
+ }
+
+}
diff --git a/hostsidetests/devicepolicy/app/ManagedProfile/AndroidManifest.xml b/hostsidetests/devicepolicy/app/ManagedProfile/AndroidManifest.xml
index aeb6a45..82f6d6d 100644
--- a/hostsidetests/devicepolicy/app/ManagedProfile/AndroidManifest.xml
+++ b/hostsidetests/devicepolicy/app/ManagedProfile/AndroidManifest.xml
@@ -34,6 +34,15 @@
<action android:name="android.app.action.DEVICE_ADMIN_ENABLED" />
</intent-filter>
</receiver>
+ <receiver
+ android:name="com.android.cts.managedprofile.PrimaryUserDeviceAdmin"
+ android:permission="android.permission.BIND_DEVICE_ADMIN">
+ <meta-data android:name="android.app.device_admin"
+ android:resource="@xml/primary_device_admin" />
+ <intent-filter>
+ <action android:name="android.app.action.DEVICE_ADMIN_ENABLED" />
+ </intent-filter>
+ </receiver>
<activity android:name=".PrimaryUserFilterSetterActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
diff --git a/hostsidetests/devicepolicy/app/ManagedProfile/res/xml/primary_device_admin.xml b/hostsidetests/devicepolicy/app/ManagedProfile/res/xml/primary_device_admin.xml
new file mode 100644
index 0000000..a6aff49
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/ManagedProfile/res/xml/primary_device_admin.xml
@@ -0,0 +1,20 @@
+<!-- Copyright (C) 2015 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.
+-->
+<device-admin xmlns:android="http://schemas.android.com/apk/res/android" android:visible="false">
+ <uses-policies>
+ <reset-password />
+ <limit-password />
+ </uses-policies>
+</device-admin>
diff --git a/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/DelegatedCertInstallerTest.java b/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/DelegatedCertInstallerTest.java
new file mode 100644
index 0000000..de859b4
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/DelegatedCertInstallerTest.java
@@ -0,0 +1,293 @@
+/*
+ * Copyright (C) 2015 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.cts.managedprofile;
+
+import android.app.KeyguardManager;
+import android.app.admin.DevicePolicyManager;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.security.KeyChain;
+import android.security.KeyChainException;
+import android.test.AndroidTestCase;
+import android.util.Base64;
+import android.util.Base64InputStream;
+
+import java.io.ByteArrayInputStream;
+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.Semaphore;
+import java.util.concurrent.TimeUnit;
+
+import static com.android.cts.managedprofile.BaseManagedProfileTest.ADMIN_RECEIVER_COMPONENT;
+
+/**
+ * Exercise delegated cert installer APIs in {@link DevicePolicyManager} by setting the test app
+ * (CtsCertInstallerApp) as a delegated cert installer and then asking it to invoke various
+ * cert-related APIs. The expected certificate changes are validated both remotely and locally.
+ */
+public class DelegatedCertInstallerTest extends AndroidTestCase {
+
+ private static final String CERT_INSTALLER_PACKAGE = "com.android.cts.certinstaller";
+
+ private static final String ACTION_INSTALL_CERT = "com.android.cts.certinstaller.install_cert";
+ private static final String ACTION_REMOVE_CERT = "com.android.cts.certinstaller.remove_cert";
+ private static final String ACTION_VERIFY_CERT = "com.android.cts.certinstaller.verify_cert";
+ private static final String ACTION_INSTALL_KEYPAIR =
+ "com.android.cts.certinstaller.install_keypair";
+ private static final String ACTION_CERT_OPERATION_DONE = "com.android.cts.certinstaller.done";
+
+ private static final String EXTRA_CERT_DATA = "extra_cert_data";
+ private static final String EXTRA_KEY_DATA = "extra_key_data";
+ private static final String EXTRA_KEY_ALIAS = "extra_key_alias";
+ private static final String EXTRA_RESULT_VALUE = "extra_result_value";
+ private static final String EXTRA_RESULT_EXCEPTION = "extra_result_exception";
+
+ /*
+ * The CA and keypair below are generated with:
+ *
+ * openssl req -new -x509 -days 3650 -extensions v3_ca -keyout cakey.pem -out cacert.pem
+ * openssl req -newkey rsa:1024 -keyout userkey.pem -nodes -days 3650 -out userkey.req
+ * mkdir -p demoCA/newcerts
+ * touch demoCA/index.txt
+ * echo "01" > demoCA/serial
+ * openssl ca -out usercert.pem -in userkey.req -cert cacert.pem -keyfile cakey.pem -days 3650
+ */
+
+ // Content from cacert.pem
+ private static final String TEST_CA =
+ "-----BEGIN CERTIFICATE-----\n" +
+ "MIIDXTCCAkWgAwIBAgIJAK9Tl/F9V8kSMA0GCSqGSIb3DQEBCwUAMEUxCzAJBgNV\n" +
+ "BAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBX\n" +
+ "aWRnaXRzIFB0eSBMdGQwHhcNMTUwMzA2MTczMjExWhcNMjUwMzAzMTczMjExWjBF\n" +
+ "MQswCQYDVQQGEwJBVTETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50\n" +
+ "ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB\n" +
+ "CgKCAQEAvItOutsE75WBTgTyNAHt4JXQ3JoseaGqcC3WQij6vhrleWi5KJ0jh1/M\n" +
+ "Rpry7Fajtwwb4t8VZa0NuM2h2YALv52w1xivql88zce/HU1y7XzbXhxis9o6SCI+\n" +
+ "oVQSbPeXRgBPppFzBEh3ZqYTVhAqw451XhwdA4Aqs3wts7ddjwlUzyMdU44osCUg\n" +
+ "kVg7lfPf9sTm5IoHVcfLSCWH5n6Nr9sH3o2ksyTwxuOAvsN11F/a0mmUoPciYPp+\n" +
+ "q7DzQzdi7akRG601DZ4YVOwo6UITGvDyuAAdxl5isovUXqe6Jmz2/myTSpAKxGFs\n" +
+ "jk9oRoG6WXWB1kni490GIPjJ1OceyQIDAQABo1AwTjAdBgNVHQ4EFgQUH1QIlPKL\n" +
+ "p2OQ/AoLOjKvBW4zK3AwHwYDVR0jBBgwFoAUH1QIlPKLp2OQ/AoLOjKvBW4zK3Aw\n" +
+ "DAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAcMi4voMMJHeQLjtq8Oky\n" +
+ "Azpyk8moDwgCd4llcGj7izOkIIFqq/lyqKdtykVKUWz2bSHO5cLrtaOCiBWVlaCV\n" +
+ "DYAnnVLM8aqaA6hJDIfaGs4zmwz0dY8hVMFCuCBiLWuPfiYtbEmjHGSmpQTG6Qxn\n" +
+ "ZJlaK5CZyt5pgh5EdNdvQmDEbKGmu0wpCq9qjZImwdyAul1t/B0DrsWApZMgZpeI\n" +
+ "d2od0VBrCICB1K4p+C51D93xyQiva7xQcCne+TAnGNy9+gjQ/MyR8MRpwRLv5ikD\n" +
+ "u0anJCN8pXo6IMglfMAsoton1J6o5/ae5uhC6caQU8bNUsCK570gpNfjkzo6rbP0\n" +
+ "wQ==\n" +
+ "-----END CERTIFICATE-----";
+ // Content from userkey.pem without the private key header and footer.
+ private static final String TEST_KEY =
+ "MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBALCYprGsTU+5L3KM\n" +
+ "fhkm0gXM2xjGUH+543YLiMPGVr3eVS7biue1/tQlL+fJsw3rqsPKJe71RbVWlpqU\n" +
+ "mhegxG4s3IvGYVB0KZoRIjDKmnnvlx6nngL2ZJ8O27U42pHsw4z4MKlcQlWkjL3T\n" +
+ "9sV6zW2Wzri+f5mvzKjhnArbLktHAgMBAAECgYBlfVVPhtZnmuXJzzQpAEZzTugb\n" +
+ "tN1OimZO0RIocTQoqj4KT+HkiJOLGFQPwbtFpMre+q4SRqNpM/oZnI1yRtKcCmIc\n" +
+ "mZgkwJ2k6pdSxqO0ofxFFTdT9czJ3rCnqBHy1g6BqUQFXT4olcygkxUpKYUwzlz1\n" +
+ "oAl487CoPxyr4sVEAQJBANwiUOHcdGd2RoRILDzw5WOXWBoWPOKzX/K9wt0yL+mO\n" +
+ "wlFNFSymqo9eLheHcEq/VD9qK9rT700dCewJfWj6+bECQQDNXmWNYIxGii5NJilT\n" +
+ "OBOHiMD/F0NE178j+/kmacbhDJwpkbLYXaP8rW4+Iswrm4ORJ59lvjNuXaZ28+sx\n" +
+ "fFp3AkA6Z7Bl/IO135+eATgbgx6ZadIqObQ1wbm3Qbmtzl7/7KyJvZXcnuup1icM\n" +
+ "fxa//jtwB89S4+Ad6ZJ0WaA4dj5BAkEAuG7V9KmIULE388EZy8rIfyepa22Q0/qN\n" +
+ "hdt8XasRGHsio5Jdc0JlSz7ViqflhCQde/aBh/XQaoVgQeO8jKyI8QJBAJHekZDj\n" +
+ "WA0w1RsBVVReN1dVXgjm1CykeAT8Qx8TUmBUfiDX6w6+eGQjKtS7f4KC2IdRTV6+\n" +
+ "bDzDoHBChHNC9ms=\n";
+
+ // Content from usercert.pem without the header and footer.
+ private static final String TEST_CERT =
+ "MIIDEjCCAfqgAwIBAgIBATANBgkqhkiG9w0BAQsFADBFMQswCQYDVQQGEwJBVTET\n" +
+ "MBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0cyBQ\n" +
+ "dHkgTHRkMB4XDTE1MDUwMTE2NTQwNVoXDTI1MDQyODE2NTQwNVowWzELMAkGA1UE\n" +
+ "BhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoMGEludGVybmV0IFdp\n" +
+ "ZGdpdHMgUHR5IEx0ZDEUMBIGA1UEAwwLY2xpZW50IGNlcnQwgZ8wDQYJKoZIhvcN\n" +
+ "AQEBBQADgY0AMIGJAoGBALCYprGsTU+5L3KMfhkm0gXM2xjGUH+543YLiMPGVr3e\n" +
+ "VS7biue1/tQlL+fJsw3rqsPKJe71RbVWlpqUmhegxG4s3IvGYVB0KZoRIjDKmnnv\n" +
+ "lx6nngL2ZJ8O27U42pHsw4z4MKlcQlWkjL3T9sV6zW2Wzri+f5mvzKjhnArbLktH\n" +
+ "AgMBAAGjezB5MAkGA1UdEwQCMAAwLAYJYIZIAYb4QgENBB8WHU9wZW5TU0wgR2Vu\n" +
+ "ZXJhdGVkIENlcnRpZmljYXRlMB0GA1UdDgQWBBQ8GL+jKSarvTn9fVNA2AzjY7qq\n" +
+ "gjAfBgNVHSMEGDAWgBRzBBA5sNWyT/fK8GrhN3tOqO5tgjANBgkqhkiG9w0BAQsF\n" +
+ "AAOCAQEAgwQEd2bktIDZZi/UOwU1jJUgGq7NiuBDPHcqgzjxhGFLQ8SQAAP3v3PR\n" +
+ "mLzcfxsxnzGynqN5iHQT4rYXxxaqrp1iIdj9xl9Wl5FxjZgXITxhlRscOd/UOBvG\n" +
+ "oMrazVczjjdoRIFFnjtU3Jf0Mich68HD1Z0S3o7X6sDYh6FTVR5KbLcxbk6RcoG4\n" +
+ "VCI5boR5LUXgb5Ed5UxczxvN12S71fyxHYVpuuI0z0HTIbAxKeRw43I6HWOmR1/0\n" +
+ "G6byGCNL/1Fz7Y+264fGqABSNTKdZwIU2K4ANEH7F+9scnhoO6OBp+gjBe5O+7jb\n" +
+ "wZmUCAoTka4hmoaOCj7cqt/IkmxozQ==\n";
+
+ private DevicePolicyManager mDpm;
+ private volatile boolean mReceivedResult;
+ private volatile Exception mReceivedException;
+ private Semaphore mAvailableResultSemaphore;
+
+ private final BroadcastReceiver receiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (ACTION_CERT_OPERATION_DONE.equals(intent.getAction())) {
+ synchronized (DelegatedCertInstallerTest.this) {
+ mReceivedResult = intent.getBooleanExtra(EXTRA_RESULT_VALUE, false);
+ mReceivedException =
+ (Exception) intent.getSerializableExtra(EXTRA_RESULT_EXCEPTION);
+ mAvailableResultSemaphore.release();
+ }
+ }
+ }
+ };
+
+ @Override
+ public void setUp() throws Exception {
+ super.setUp();
+ mDpm = (DevicePolicyManager) mContext.getSystemService(Context.DEVICE_POLICY_SERVICE);
+ mAvailableResultSemaphore = new Semaphore(0);
+ mReceivedResult = false;
+ mReceivedException = null;
+ IntentFilter filter = new IntentFilter();
+ filter.addAction(ACTION_CERT_OPERATION_DONE);
+ getContext().registerReceiver(receiver, filter);
+ }
+
+ @Override
+ public void tearDown() throws Exception {
+ getContext().unregisterReceiver(receiver);
+ mDpm.uninstallCaCert(ADMIN_RECEIVER_COMPONENT, TEST_CA.getBytes());
+ // Installed private key pair will be removed once the lockscreen password is cleared,
+ // which is done in the hostside test.
+ mDpm.setCertInstallerPackage(ADMIN_RECEIVER_COMPONENT, null);
+ super.tearDown();
+ }
+
+ public void testCaCertsOperations() throws InterruptedException {
+ byte[] cert = TEST_CA.getBytes();
+
+ mDpm.setCertInstallerPackage(ADMIN_RECEIVER_COMPONENT, CERT_INSTALLER_PACKAGE);
+ assertEquals(CERT_INSTALLER_PACKAGE,
+ mDpm.getCertInstallerPackage(ADMIN_RECEIVER_COMPONENT));
+
+ // Exercise installCaCert()
+ installCaCert(cert);
+ assertResult("installCaCert", true);
+ assertTrue("Certificate is not installed properly", mDpm.hasCaCertInstalled(
+ ADMIN_RECEIVER_COMPONENT, cert));
+
+ // Exercise getInstalledCaCerts()
+ verifyCaCert(cert);
+ assertResult("getInstalledCaCerts()", true);
+
+ // Exercise uninstallCaCert()
+ removeCaCert(cert);
+ assertResult("uninstallCaCert()", true);
+ assertFalse("Certificate is not removed properly", mDpm.hasCaCertInstalled(
+ ADMIN_RECEIVER_COMPONENT, cert));
+
+ // Clear delegated cert installer.
+ // Tests after this are expected to fail.
+ mDpm.setCertInstallerPackage(ADMIN_RECEIVER_COMPONENT, null);
+
+ installCaCert(cert);
+ assertResult("installCaCert", false);
+ }
+
+ public void testInstallKeyPair() throws InterruptedException, KeyChainException {
+ final String alias = "delegated-cert-installer-test-key";
+
+ // Clear delegated cert installer.
+ mDpm.setCertInstallerPackage(ADMIN_RECEIVER_COMPONENT, null);
+ // The app is not the cert installer , it shouldn't have have privilege to call
+ // installKeyPair().
+ installKeyPair(TEST_KEY, TEST_CERT, alias);
+ assertResult("installKeyPair", false);
+
+ // Set the app to be cert installer.
+ mDpm.setCertInstallerPackage(ADMIN_RECEIVER_COMPONENT, CERT_INSTALLER_PACKAGE);
+ assertEquals(CERT_INSTALLER_PACKAGE,
+ mDpm.getCertInstallerPackage(ADMIN_RECEIVER_COMPONENT));
+
+ // Exercise installKeyPair()
+ checkKeyguardPrecondition();
+ installKeyPair(TEST_KEY, TEST_CERT, alias);
+ assertResult("installKeyPair", true);
+ }
+
+ /**
+ * installKeyPair() requires the system to have a lockscreen password, which should have been
+ * set by the host side test.
+ */
+ private void checkKeyguardPrecondition() throws InterruptedException {
+ KeyguardManager km = (KeyguardManager) mContext.getSystemService(Context.KEYGUARD_SERVICE);
+ if (!km.isKeyguardSecure()) {
+ Thread.sleep(5000);
+ }
+ assertTrue("A lockscreen password is required before keypair can be installed",
+ km.isKeyguardSecure());
+ }
+
+ private void installCaCert(byte[] cert) {
+ Intent intent = new Intent();
+ intent.setAction(ACTION_INSTALL_CERT);
+ intent.putExtra(EXTRA_CERT_DATA, cert);
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ getContext().startActivity(intent);
+ }
+
+ private void removeCaCert(byte[] cert) {
+ Intent intent = new Intent();
+ intent.setAction(ACTION_REMOVE_CERT);
+ intent.putExtra(EXTRA_CERT_DATA, cert);
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ getContext().startActivity(intent);
+ }
+
+ private void verifyCaCert(byte[] cert) {
+ Intent intent = new Intent();
+ intent.setAction(ACTION_VERIFY_CERT);
+ intent.putExtra(EXTRA_CERT_DATA, cert);
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ getContext().startActivity(intent);
+ }
+
+ private void assertResult(String testName, Boolean expectSuccess) throws InterruptedException {
+ assertTrue("Cert installer did not respond in time.",
+ mAvailableResultSemaphore.tryAcquire(5, TimeUnit.SECONDS));
+ synchronized (this) {
+ if (expectSuccess) {
+ assertTrue(testName + " failed unexpectedly.", mReceivedResult);
+ assertNull(testName + " raised exception", mReceivedException);
+ } else {
+ assertFalse(testName + " succeeded unexpectedly.", mReceivedResult);
+ assertTrue(testName + " did not raise SecurityException",
+ mReceivedException != null &&
+ mReceivedException instanceof SecurityException);
+ }
+ }
+ }
+
+ private void installKeyPair(String key, String cert, String alias) {
+ Intent intent = new Intent();
+ intent.setAction(ACTION_INSTALL_KEYPAIR);
+ intent.putExtra(EXTRA_CERT_DATA, cert);
+ intent.putExtra(EXTRA_KEY_DATA, key);
+ intent.putExtra(EXTRA_KEY_ALIAS, alias);
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ getContext().startActivity(intent);
+ }
+}
diff --git a/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/PrimaryUserAdminHelper.java b/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/PrimaryUserAdminHelper.java
new file mode 100644
index 0000000..5b8af1f
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/PrimaryUserAdminHelper.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2015 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.cts.managedprofile;
+
+import android.app.admin.DevicePolicyManager;
+import android.content.ComponentName;
+import android.content.Context;
+import android.test.AndroidTestCase;
+
+/**
+ * This test executes helper tasks as active device admin in the primary user. Current tasks are
+ * setting and clearing lockscreen password used by the host side delegated cert installer test.
+ */
+public class PrimaryUserAdminHelper extends AndroidTestCase {
+
+ private DevicePolicyManager mDpm;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ mDpm = (DevicePolicyManager) mContext.getSystemService(Context.DEVICE_POLICY_SERVICE);
+ }
+
+ /**
+ * Device admin can only be deactivated by itself and this test should be executed before the
+ * device admin package can be uninstalled.
+ */
+ public void testClearDeviceAdmin() {
+ try {
+ removeActiveAdmin(mDpm, PrimaryUserDeviceAdmin.ADMIN_RECEIVER_COMPONENT);
+ } catch (InterruptedException e) {
+ fail("Removal of device admin interrupted.");
+ }
+ assertFalse(mDpm.isAdminActive(PrimaryUserDeviceAdmin.ADMIN_RECEIVER_COMPONENT));
+ }
+
+ /**
+ * Set lockscreen password.
+ */
+ public void testSetPassword() {
+ // Enable credential storage by setting a nonempty password.
+ assertTrue(mDpm.resetPassword("test", 0));
+ }
+
+ /**
+ * Clear lockscreen password.
+ */
+ public void testClearPassword() {
+ mDpm.setPasswordQuality(PrimaryUserDeviceAdmin.ADMIN_RECEIVER_COMPONENT,
+ DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED);
+ mDpm.setPasswordMinimumLength(
+ PrimaryUserDeviceAdmin.ADMIN_RECEIVER_COMPONENT, 0);
+ assertTrue(mDpm.resetPassword("", 0));
+ }
+
+ private void removeActiveAdmin(DevicePolicyManager dpm, ComponentName cn)
+ throws InterruptedException {
+ if (mDpm.isAdminActive(cn)) {
+ mDpm.removeActiveAdmin(cn);
+ // Wait until device admin is not active.
+ for (int i = 0; i < 1000 && dpm.isAdminActive(cn); i++) {
+ Thread.sleep(100);
+ }
+ }
+ }
+}
diff --git a/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/PrimaryUserDeviceAdmin.java b/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/PrimaryUserDeviceAdmin.java
new file mode 100644
index 0000000..8662daf
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/PrimaryUserDeviceAdmin.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2015 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.cts.managedprofile;
+
+import android.app.admin.DeviceAdminReceiver;
+import android.content.ComponentName;
+
+/**
+ * A device admin class running in the primary user. Currently used by delegated cert installer
+ * test to set a lockscreen password which is prerequisite of installKeyPair().
+ */
+public class PrimaryUserDeviceAdmin extends DeviceAdminReceiver {
+ public static final ComponentName ADMIN_RECEIVER_COMPONENT = new ComponentName(
+ PrimaryUserDeviceAdmin.class.getPackage().getName(),
+ PrimaryUserDeviceAdmin.class.getName());
+}
\ No newline at end of file
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/BaseDevicePolicyTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/BaseDevicePolicyTest.java
index 7e0d959..5904a6f 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/BaseDevicePolicyTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/BaseDevicePolicyTest.java
@@ -335,4 +335,12 @@
assertTrue(commandOutput + " expected to start with \"Success:\"",
commandOutput.startsWith("Success:"));
}
+
+ protected void setDeviceAdmin(String componentName) throws DeviceNotAvailableException {
+ String command = "dpm set-active-admin '" + componentName + "'";
+ String commandOutput = getDevice().executeShellCommand(command);
+ CLog.logAndDisplay(LogLevel.INFO, "Output for command " + command + ": " + commandOutput);
+ assertTrue(commandOutput + " expected to start with \"Success:\"",
+ commandOutput.startsWith("Success:"));
+ }
}
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ManagedProfileTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ManagedProfileTest.java
index 3954bda..5c2048e 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ManagedProfileTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ManagedProfileTest.java
@@ -36,6 +36,9 @@
private static final String INTENT_RECEIVER_PKG = "com.android.cts.intent.receiver";
private static final String INTENT_RECEIVER_APK = "CtsIntentReceiverApp.apk";
+ private static final String CERT_INSTALLER_PKG = "com.android.cts.certinstaller";
+ private static final String CERT_INSTALLER_APK = "CtsCertInstallerApp.apk";
+
private static final String ADMIN_RECEIVER_TEST_CLASS =
MANAGED_PROFILE_PKG + ".BaseManagedProfileTest$BasicAdminReceiver";
@@ -66,6 +69,7 @@
getDevice().uninstallPackage(MANAGED_PROFILE_PKG);
getDevice().uninstallPackage(INTENT_SENDER_PKG);
getDevice().uninstallPackage(INTENT_RECEIVER_PKG);
+ getDevice().uninstallPackage(CERT_INSTALLER_PKG);
}
super.tearDown();
}
@@ -380,6 +384,28 @@
"testSetBluetoothContactSharingDisabled_setterAndGetter", mUserId));
}
+ public void testDelegatedCertInstaller() throws Exception {
+ if (!mHasFeature) {
+ return;
+ }
+ installApp(CERT_INSTALLER_APK);
+ setDeviceAdmin(MANAGED_PROFILE_PKG + "/.PrimaryUserDeviceAdmin");
+
+ final String adminHelperClass = ".PrimaryUserAdminHelper";
+ try {
+ assertTrue("Set lockscreen password failed", runDeviceTestsAsUser(MANAGED_PROFILE_PKG,
+ adminHelperClass, "testSetPassword", 0 /* user 0 */));
+ assertTrue("DelegatedCertInstaller failed", runDeviceTestsAsUser(MANAGED_PROFILE_PKG,
+ ".DelegatedCertInstallerTest", mUserId));
+ } finally {
+ // Reset lockscreen password and remove device admin.
+ assertTrue("Clear lockscreen password failed", runDeviceTestsAsUser(MANAGED_PROFILE_PKG,
+ adminHelperClass, "testClearPassword", 0 /* user 0 */));
+ assertTrue("Clear device admin failed", runDeviceTestsAsUser(MANAGED_PROFILE_PKG,
+ adminHelperClass, "testClearDeviceAdmin", 0 /* user 0 */));
+ }
+ }
+
private void disableActivityForUser(String activityName, int userId)
throws DeviceNotAvailableException {
String command = "am start -W --user " + userId
diff --git a/tests/tests/admin/src/android/admin/cts/DevicePolicyManagerTest.java b/tests/tests/admin/src/android/admin/cts/DevicePolicyManagerTest.java
index 08a905b..41332c7 100644
--- a/tests/tests/admin/src/android/admin/cts/DevicePolicyManagerTest.java
+++ b/tests/tests/admin/src/android/admin/cts/DevicePolicyManagerTest.java
@@ -679,6 +679,19 @@
}
}
+ public void testInstallCaCert_failIfNotCertInstaller() {
+ if (!mDeviceAdmin) {
+ Log.w(TAG, "Skipping testInstallCaCert_failIfNotCertInstaller");
+ return;
+ }
+ try {
+ // Delegated cert installer is identified by using null as the first argument.
+ mDevicePolicyManager.installCaCert(null, TEST_CA_STRING1.getBytes());
+ fail("did not throw expected SecurityException");
+ } catch (SecurityException expected) {
+ }
+ }
+
public void testUninstallCaCert_failIfNotProfileOwner() {
if (!mDeviceAdmin) {
Log.w(TAG, "Skipping testUninstallCaCert_failIfNotProfileOwner");
@@ -693,6 +706,19 @@
}
}
+ public void testUninstallCaCert_failIfNotCertInstaller() {
+ if (!mDeviceAdmin) {
+ Log.w(TAG, "Skipping testUninstallCaCert_failIfNotCertInstaller");
+ return;
+ }
+ try {
+ // Delegated cert installer is identified by using null as the first argument.
+ mDevicePolicyManager.uninstallCaCert(null, TEST_CA_STRING1.getBytes());
+ fail("did not throw expected SecurityException");
+ } catch (SecurityException expected) {
+ }
+ }
+
public void testGetInstalledCaCerts_failIfNotProfileOwner() {
if (!mDeviceAdmin) {
Log.w(TAG, "Skipping testGetInstalledCaCerts_failIfNotProfileOwner");
@@ -706,6 +732,19 @@
}
}
+ public void testGetInstalledCaCerts_failIfNotCertInstaller() {
+ if (!mDeviceAdmin) {
+ Log.w(TAG, "Skipping testGetInstalledCaCerts_failIfNotCertInstaller");
+ return;
+ }
+ try {
+ // Delegated cert installer is identified by using null as the first argument.
+ mDevicePolicyManager.getInstalledCaCerts(null);
+ fail("did not throw expected SecurityException");
+ } catch (SecurityException expected) {
+ }
+ }
+
public void testHasCaCertInstalled_failIfNotProfileOwner() {
if (!mDeviceAdmin) {
Log.w(TAG, "Skipping testHasCaCertInstalled_failIfNotProfileOwner");
@@ -720,6 +759,19 @@
}
}
+ public void testHasCaCertInstalled_failIfNotCertInstaller() {
+ if (!mDeviceAdmin) {
+ Log.w(TAG, "Skipping testHasCaCertInstalled_failIfNotCertInstaller");
+ return;
+ }
+ try {
+ // Delegated cert installer is identified by using null as the first argument.
+ mDevicePolicyManager.hasCaCertInstalled(null, TEST_CA_STRING1.getBytes());
+ fail("did not throw expected SecurityException");
+ } catch (SecurityException expected) {
+ }
+ }
+
public void testUninstallAllUserCaCerts_failIfNotProfileOwner() {
if (!mDeviceAdmin) {
Log.w(TAG, "Skipping testUninstallAllUserCaCerts_failIfNotProfileOwner");
@@ -733,6 +785,19 @@
}
}
+ public void testUninstallAllUserCaCerts_failIfNotCertInstaller() {
+ if (!mDeviceAdmin) {
+ Log.w(TAG, "Skipping testUninstallAllUserCaCerts_failIfNotCertInstaller");
+ return;
+ }
+ try {
+ // Delegated cert installer is identified by using null as the first argument.
+ mDevicePolicyManager.uninstallAllUserCaCerts(null);
+ fail("did not throw expected SecurityException");
+ } catch (SecurityException expected) {
+ }
+ }
+
public void testSetScreenCaptureDisabled_failIfNotProfileOwner() {
if (!mDeviceAdmin) {
Log.w(TAG, "Skipping testSetScreenCaptureDisabled_failIfNotProfileOwner");
@@ -961,4 +1026,30 @@
assertTrue("Password '" + password + "' failed on " + restriction, passwordResetResult);
assertTrue(mDevicePolicyManager.isActivePasswordSufficient());
}
+
+ public void testSetDelegatedCertInstaller_failIfNotProfileOwner() {
+ if (!mDeviceAdmin) {
+ Log.w(TAG, "Skipping testSetDelegatedCertInstaller_failIfNotProfileOwner");
+ return;
+ }
+ try {
+ mDevicePolicyManager.setCertInstallerPackage(mComponent, "com.test.package");
+ fail("did not throw expected SecurityException");
+ } catch (SecurityException e) {
+ assertProfileOwnerMessage(e.getMessage());
+ }
+ }
+
+ public void testGetDelegatedCertInstaller_failIfNotProfileOwner() {
+ if (!mDeviceAdmin) {
+ Log.w(TAG, "Skipping testGetDelegatedCertInstaller_failIfNotProfileOwner");
+ return;
+ }
+ try {
+ mDevicePolicyManager.getCertInstallerPackage(mComponent);
+ fail("did not throw expected SecurityException");
+ } catch (SecurityException e) {
+ assertProfileOwnerMessage(e.getMessage());
+ }
+ }
}