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