1 /*
2  * Copyright (C) 2015 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  */
16 
17 package com.android.cts.deviceandprofileowner;
18 
19 import android.app.KeyguardManager;
20 import android.app.admin.DevicePolicyManager;
21 import android.content.BroadcastReceiver;
22 import android.content.ComponentName;
23 import android.content.Context;
24 import android.content.Intent;
25 import android.content.IntentFilter;
26 import android.os.Build;
27 import android.os.Process;
28 import android.os.UserHandle;
29 import android.security.KeyChainException;
30 import android.test.MoreAsserts;
31 
32 import com.android.org.conscrypt.TrustedCertificateStore;
33 
34 import java.io.ByteArrayInputStream;
35 import java.security.cert.Certificate;
36 import java.security.cert.CertificateException;
37 import java.security.cert.CertificateFactory;
38 import java.util.List;
39 import java.util.concurrent.Semaphore;
40 import java.util.concurrent.TimeUnit;
41 
42 /**
43  * Exercise delegated cert installer APIs in {@link DevicePolicyManager} by setting the test app
44  * (CtsCertInstallerApp) as a delegated cert installer and then asking it to invoke various
45  * cert-related APIs. The expected certificate changes are validated both remotely and locally.
46  */
47 public class DelegatedCertInstallerTest extends BaseDeviceAdminTest {
48 
49     private static final String CERT_INSTALLER_PACKAGE = "com.android.cts.certinstaller";
50     private static final String NOT_EXIST_CERT_INSTALLER_PACKAGE
51             = "com.android.cts.certinstaller.not_exist";
52 
53     private static final String ACTION_INSTALL_CERT = "com.android.cts.certinstaller.install_cert";
54     private static final String ACTION_REMOVE_CERT = "com.android.cts.certinstaller.remove_cert";
55     private static final String ACTION_VERIFY_CERT = "com.android.cts.certinstaller.verify_cert";
56     private static final String ACTION_INSTALL_KEYPAIR =
57             "com.android.cts.certinstaller.install_keypair";
58     private static final String ACTION_CERT_OPERATION_DONE = "com.android.cts.certinstaller.done";
59 
60     private static final String EXTRA_CERT_DATA = "extra_cert_data";
61     private static final String EXTRA_KEY_DATA = "extra_key_data";
62     private static final String EXTRA_KEY_ALIAS = "extra_key_alias";
63     private static final String EXTRA_RESULT_VALUE = "extra_result_value";
64     private static final String EXTRA_RESULT_EXCEPTION = "extra_result_exception";
65     // package name of receiver has to be specified explicitly as the receiver is registered in
66     // manifest
67     private static final ComponentName CERT_INSTALLER_COMPONENT = new ComponentName(
68             CERT_INSTALLER_PACKAGE, "com.android.cts.certinstaller.CertInstallerReceiver");
69 
70     /*
71      * The CA and keypair below are generated with:
72      *
73      * openssl req -new -x509 -days 3650 -extensions v3_ca -keyout cakey.pem -out cacert.pem
74      * openssl req -newkey rsa:1024 -keyout userkey.pem -nodes -days 3650 -out userkey.req
75      * mkdir -p demoCA/newcerts
76      * touch demoCA/index.txt
77      * echo "01" > demoCA/serial
78      * openssl ca -out usercert.pem -in userkey.req -cert cacert.pem -keyfile cakey.pem -days 3650
79      */
80 
81      // Content from cacert.pem
82     private static final String TEST_CA =
83             "-----BEGIN CERTIFICATE-----\n" +
84             "MIIDXTCCAkWgAwIBAgIJAK9Tl/F9V8kSMA0GCSqGSIb3DQEBCwUAMEUxCzAJBgNV\n" +
85             "BAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBX\n" +
86             "aWRnaXRzIFB0eSBMdGQwHhcNMTUwMzA2MTczMjExWhcNMjUwMzAzMTczMjExWjBF\n" +
87             "MQswCQYDVQQGEwJBVTETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50\n" +
88             "ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB\n" +
89             "CgKCAQEAvItOutsE75WBTgTyNAHt4JXQ3JoseaGqcC3WQij6vhrleWi5KJ0jh1/M\n" +
90             "Rpry7Fajtwwb4t8VZa0NuM2h2YALv52w1xivql88zce/HU1y7XzbXhxis9o6SCI+\n" +
91             "oVQSbPeXRgBPppFzBEh3ZqYTVhAqw451XhwdA4Aqs3wts7ddjwlUzyMdU44osCUg\n" +
92             "kVg7lfPf9sTm5IoHVcfLSCWH5n6Nr9sH3o2ksyTwxuOAvsN11F/a0mmUoPciYPp+\n" +
93             "q7DzQzdi7akRG601DZ4YVOwo6UITGvDyuAAdxl5isovUXqe6Jmz2/myTSpAKxGFs\n" +
94             "jk9oRoG6WXWB1kni490GIPjJ1OceyQIDAQABo1AwTjAdBgNVHQ4EFgQUH1QIlPKL\n" +
95             "p2OQ/AoLOjKvBW4zK3AwHwYDVR0jBBgwFoAUH1QIlPKLp2OQ/AoLOjKvBW4zK3Aw\n" +
96             "DAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAcMi4voMMJHeQLjtq8Oky\n" +
97             "Azpyk8moDwgCd4llcGj7izOkIIFqq/lyqKdtykVKUWz2bSHO5cLrtaOCiBWVlaCV\n" +
98             "DYAnnVLM8aqaA6hJDIfaGs4zmwz0dY8hVMFCuCBiLWuPfiYtbEmjHGSmpQTG6Qxn\n" +
99             "ZJlaK5CZyt5pgh5EdNdvQmDEbKGmu0wpCq9qjZImwdyAul1t/B0DrsWApZMgZpeI\n" +
100             "d2od0VBrCICB1K4p+C51D93xyQiva7xQcCne+TAnGNy9+gjQ/MyR8MRpwRLv5ikD\n" +
101             "u0anJCN8pXo6IMglfMAsoton1J6o5/ae5uhC6caQU8bNUsCK570gpNfjkzo6rbP0\n" +
102             "wQ==\n" +
103             "-----END CERTIFICATE-----";
104     // Content from userkey.pem without the private key header and footer.
105     private static final String TEST_KEY =
106             "MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBALCYprGsTU+5L3KM\n" +
107             "fhkm0gXM2xjGUH+543YLiMPGVr3eVS7biue1/tQlL+fJsw3rqsPKJe71RbVWlpqU\n" +
108             "mhegxG4s3IvGYVB0KZoRIjDKmnnvlx6nngL2ZJ8O27U42pHsw4z4MKlcQlWkjL3T\n" +
109             "9sV6zW2Wzri+f5mvzKjhnArbLktHAgMBAAECgYBlfVVPhtZnmuXJzzQpAEZzTugb\n" +
110             "tN1OimZO0RIocTQoqj4KT+HkiJOLGFQPwbtFpMre+q4SRqNpM/oZnI1yRtKcCmIc\n" +
111             "mZgkwJ2k6pdSxqO0ofxFFTdT9czJ3rCnqBHy1g6BqUQFXT4olcygkxUpKYUwzlz1\n" +
112             "oAl487CoPxyr4sVEAQJBANwiUOHcdGd2RoRILDzw5WOXWBoWPOKzX/K9wt0yL+mO\n" +
113             "wlFNFSymqo9eLheHcEq/VD9qK9rT700dCewJfWj6+bECQQDNXmWNYIxGii5NJilT\n" +
114             "OBOHiMD/F0NE178j+/kmacbhDJwpkbLYXaP8rW4+Iswrm4ORJ59lvjNuXaZ28+sx\n" +
115             "fFp3AkA6Z7Bl/IO135+eATgbgx6ZadIqObQ1wbm3Qbmtzl7/7KyJvZXcnuup1icM\n" +
116             "fxa//jtwB89S4+Ad6ZJ0WaA4dj5BAkEAuG7V9KmIULE388EZy8rIfyepa22Q0/qN\n" +
117             "hdt8XasRGHsio5Jdc0JlSz7ViqflhCQde/aBh/XQaoVgQeO8jKyI8QJBAJHekZDj\n" +
118             "WA0w1RsBVVReN1dVXgjm1CykeAT8Qx8TUmBUfiDX6w6+eGQjKtS7f4KC2IdRTV6+\n" +
119             "bDzDoHBChHNC9ms=\n";
120 
121     // Content from usercert.pem without the header and footer.
122     private static final String TEST_CERT =
123             "MIIDEjCCAfqgAwIBAgIBATANBgkqhkiG9w0BAQsFADBFMQswCQYDVQQGEwJBVTET\n" +
124             "MBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0cyBQ\n" +
125             "dHkgTHRkMB4XDTE1MDUwMTE2NTQwNVoXDTI1MDQyODE2NTQwNVowWzELMAkGA1UE\n" +
126             "BhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoMGEludGVybmV0IFdp\n" +
127             "ZGdpdHMgUHR5IEx0ZDEUMBIGA1UEAwwLY2xpZW50IGNlcnQwgZ8wDQYJKoZIhvcN\n" +
128             "AQEBBQADgY0AMIGJAoGBALCYprGsTU+5L3KMfhkm0gXM2xjGUH+543YLiMPGVr3e\n" +
129             "VS7biue1/tQlL+fJsw3rqsPKJe71RbVWlpqUmhegxG4s3IvGYVB0KZoRIjDKmnnv\n" +
130             "lx6nngL2ZJ8O27U42pHsw4z4MKlcQlWkjL3T9sV6zW2Wzri+f5mvzKjhnArbLktH\n" +
131             "AgMBAAGjezB5MAkGA1UdEwQCMAAwLAYJYIZIAYb4QgENBB8WHU9wZW5TU0wgR2Vu\n" +
132             "ZXJhdGVkIENlcnRpZmljYXRlMB0GA1UdDgQWBBQ8GL+jKSarvTn9fVNA2AzjY7qq\n" +
133             "gjAfBgNVHSMEGDAWgBRzBBA5sNWyT/fK8GrhN3tOqO5tgjANBgkqhkiG9w0BAQsF\n" +
134             "AAOCAQEAgwQEd2bktIDZZi/UOwU1jJUgGq7NiuBDPHcqgzjxhGFLQ8SQAAP3v3PR\n" +
135             "mLzcfxsxnzGynqN5iHQT4rYXxxaqrp1iIdj9xl9Wl5FxjZgXITxhlRscOd/UOBvG\n" +
136             "oMrazVczjjdoRIFFnjtU3Jf0Mich68HD1Z0S3o7X6sDYh6FTVR5KbLcxbk6RcoG4\n" +
137             "VCI5boR5LUXgb5Ed5UxczxvN12S71fyxHYVpuuI0z0HTIbAxKeRw43I6HWOmR1/0\n" +
138             "G6byGCNL/1Fz7Y+264fGqABSNTKdZwIU2K4ANEH7F+9scnhoO6OBp+gjBe5O+7jb\n" +
139             "wZmUCAoTka4hmoaOCj7cqt/IkmxozQ==\n";
140 
141     private DevicePolicyManager mDpm;
142     private volatile boolean mReceivedResult;
143     private volatile Exception mReceivedException;
144     private Semaphore mAvailableResultSemaphore;
145 
146     private final BroadcastReceiver receiver = new BroadcastReceiver() {
147         @Override
148         public void onReceive(Context context, Intent intent) {
149             if (ACTION_CERT_OPERATION_DONE.equals(intent.getAction())) {
150                 synchronized (DelegatedCertInstallerTest.this) {
151                     mReceivedResult = intent.getBooleanExtra(EXTRA_RESULT_VALUE, false);
152                     mReceivedException =
153                             (Exception) intent.getSerializableExtra(EXTRA_RESULT_EXCEPTION);
154                     mAvailableResultSemaphore.release();
155                 }
156             }
157         }
158     };
159 
160     @Override
setUp()161     public void setUp() throws Exception {
162         super.setUp();
163         mDpm = (DevicePolicyManager) mContext.getSystemService(Context.DEVICE_POLICY_SERVICE);
164         mAvailableResultSemaphore = new Semaphore(0);
165         mReceivedResult = false;
166         mReceivedException = null;
167         IntentFilter filter = new IntentFilter();
168         filter.addAction(ACTION_CERT_OPERATION_DONE);
169         mContext.registerReceiver(receiver, filter);
170     }
171 
172     @Override
tearDown()173     public void tearDown() throws Exception {
174         mContext.unregisterReceiver(receiver);
175         mDpm.uninstallCaCert(ADMIN_RECEIVER_COMPONENT, TEST_CA.getBytes());
176         // Installed private key pair will be removed once the lockscreen password is cleared,
177         // which is done in the hostside test.
178         mDpm.setCertInstallerPackage(ADMIN_RECEIVER_COMPONENT, null);
179         super.tearDown();
180     }
181 
testCaCertsOperations()182     public void testCaCertsOperations() throws InterruptedException, CertificateException {
183         final byte[] cert = TEST_CA.getBytes();
184         final Certificate caCert = CertificateFactory.getInstance("X.509")
185                 .generateCertificate(new ByteArrayInputStream(cert));
186         final TrustedCertificateStore store = new TrustedCertificateStore();
187 
188         mDpm.setCertInstallerPackage(ADMIN_RECEIVER_COMPONENT, CERT_INSTALLER_PACKAGE);
189         assertEquals(CERT_INSTALLER_PACKAGE,
190                 mDpm.getCertInstallerPackage(ADMIN_RECEIVER_COMPONENT));
191 
192         // Exercise installCaCert()
193         assertNull(store.getCertificateAlias(caCert));
194         installCaCert(cert);
195         assertResult("installCaCert", true);
196         assertTrue("Certificate is not installed properly", mDpm.hasCaCertInstalled(
197                 ADMIN_RECEIVER_COMPONENT, cert));
198 
199         // Exercise getInstalledCaCerts()
200         verifyCaCert(cert);
201         assertResult("getInstalledCaCerts()", true);
202 
203         // Verify that the CA cert was marked as installed by the Device Owner / Profile Owner.
204         final String alias = store.getCertificateAlias(caCert);
205         assertNotNull(alias);
206         verifyOwnerInstalledStatus(alias, true);
207 
208         // Exercise uninstallCaCert()
209         removeCaCert(cert);
210         assertResult("uninstallCaCert()", true);
211         assertFalse("Certificate is not removed properly", mDpm.hasCaCertInstalled(
212                 ADMIN_RECEIVER_COMPONENT, cert));
213 
214         // Verify that the CA cert is no longer reported as installed by the Device Owner / Profile
215         // Owner.
216         verifyOwnerInstalledStatus(alias, false);
217 
218         // Clear delegated cert installer.
219         // Tests after this are expected to fail.
220         mDpm.setCertInstallerPackage(ADMIN_RECEIVER_COMPONENT, null);
221 
222         installCaCert(cert);
223         assertResult("installCaCert", false);
224     }
225 
testInstallKeyPair()226     public void testInstallKeyPair() throws InterruptedException, KeyChainException {
227         final String alias = "delegated-cert-installer-test-key";
228 
229         // Clear delegated cert installer.
230         mDpm.setCertInstallerPackage(ADMIN_RECEIVER_COMPONENT, null);
231         // The app is not the cert installer , it shouldn't have have privilege to call
232         // installKeyPair().
233         installKeyPair(TEST_KEY, TEST_CERT, alias);
234         assertResult("installKeyPair", false);
235 
236         // Set the app to be cert installer.
237         mDpm.setCertInstallerPackage(ADMIN_RECEIVER_COMPONENT, CERT_INSTALLER_PACKAGE);
238         assertEquals(CERT_INSTALLER_PACKAGE,
239                 mDpm.getCertInstallerPackage(ADMIN_RECEIVER_COMPONENT));
240 
241         // Exercise installKeyPair()
242         checkKeyguardPrecondition();
243         installKeyPair(TEST_KEY, TEST_CERT, alias);
244         assertResult("installKeyPair", true);
245     }
246 
247     /**
248      * If DPC is targeting N+, @{link IllegalArgumentException } should be thrown if the package
249      * is missing.
250      */
testSetNotExistCertInstallerPackage()251     public void testSetNotExistCertInstallerPackage() throws Exception {
252         boolean shouldThrowException = getTargetApiLevel() >= Build.VERSION_CODES.N;
253         try {
254             mDpm.setCertInstallerPackage(
255                     ADMIN_RECEIVER_COMPONENT, NOT_EXIST_CERT_INSTALLER_PACKAGE);
256             if (shouldThrowException) {
257                 fail("Did not throw IllegalArgumentException");
258             }
259         } catch (IllegalArgumentException ex) {
260             if (!shouldThrowException) {
261                 fail("Should not throw exception");
262             }
263             MoreAsserts.assertContainsRegex("is not installed on the current user",
264                         ex.getMessage());
265         }
266         if (!shouldThrowException) {
267             assertTrue("Cert install delegate was not set on uninstalled package",
268                     NOT_EXIST_CERT_INSTALLER_PACKAGE.equals(
269                             mDpm.getCertInstallerPackage(ADMIN_RECEIVER_COMPONENT)));
270         }
271     }
272 
273     /**
274      * installKeyPair() requires the system to have a lockscreen password, which should have been
275      * set by the host side test.
276      */
checkKeyguardPrecondition()277     private void checkKeyguardPrecondition() throws InterruptedException {
278         KeyguardManager km = (KeyguardManager) mContext.getSystemService(Context.KEYGUARD_SERVICE);
279         if (!km.isKeyguardSecure()) {
280             Thread.sleep(5000);
281           }
282           assertTrue("A lockscreen password is required before keypair can be installed",
283                           km.isKeyguardSecure());
284     }
285 
installCaCert(byte[] cert)286     private void installCaCert(byte[] cert) {
287         Intent intent = new Intent();
288         intent.setAction(ACTION_INSTALL_CERT);
289         intent.setComponent(CERT_INSTALLER_COMPONENT);
290         intent.putExtra(EXTRA_CERT_DATA, cert);
291         intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
292         mContext.sendBroadcast(intent);
293     }
294 
removeCaCert(byte[] cert)295     private void removeCaCert(byte[] cert) {
296         Intent intent = new Intent();
297         intent.setAction(ACTION_REMOVE_CERT);
298         intent.setComponent(CERT_INSTALLER_COMPONENT);
299         intent.putExtra(EXTRA_CERT_DATA, cert);
300         intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
301         mContext.sendBroadcast(intent);
302     }
303 
verifyCaCert(byte[] cert)304     private void verifyCaCert(byte[] cert) {
305         Intent intent = new Intent();
306         intent.setAction(ACTION_VERIFY_CERT);
307         intent.setComponent(CERT_INSTALLER_COMPONENT);
308         intent.putExtra(EXTRA_CERT_DATA, cert);
309         intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
310         mContext.sendBroadcast(intent);
311     }
312 
verifyOwnerInstalledStatus(String alias, boolean expectOwnerInstalled)313     private void verifyOwnerInstalledStatus(String alias, boolean expectOwnerInstalled) {
314         final List<String> ownerInstalledCerts =
315                 mDpm.getOwnerInstalledCaCerts(Process.myUserHandle());
316         assertNotNull(ownerInstalledCerts);
317         assertEquals(expectOwnerInstalled, ownerInstalledCerts.contains(alias));
318     }
319 
assertResult(String testName, Boolean expectSuccess)320     private void assertResult(String testName, Boolean expectSuccess) throws InterruptedException {
321         assertTrue("Cert installer did not respond in time.",
322                 mAvailableResultSemaphore.tryAcquire(10, TimeUnit.SECONDS));
323         synchronized (this) {
324             if (expectSuccess) {
325                 assertTrue(testName + " failed unexpectedly.", mReceivedResult);
326                 assertNull(testName + " raised exception", mReceivedException);
327             } else {
328                 assertFalse(testName + " succeeded unexpectedly.", mReceivedResult);
329                 assertTrue(testName + " did not raise SecurityException",
330                         mReceivedException != null &&
331                         mReceivedException instanceof SecurityException);
332             }
333         }
334     }
335 
installKeyPair(String key, String cert, String alias)336     private void installKeyPair(String key, String cert, String alias) {
337         Intent intent = new Intent();
338         intent.setAction(ACTION_INSTALL_KEYPAIR);
339         intent.setComponent(CERT_INSTALLER_COMPONENT);
340         intent.putExtra(EXTRA_CERT_DATA, cert);
341         intent.putExtra(EXTRA_KEY_DATA, key);
342         intent.putExtra(EXTRA_KEY_ALIAS, alias);
343         intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
344         mContext.sendBroadcast(intent);
345     }
346 }
347