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