1 /* 2 * Copyright (C) 2018 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.certinstaller; 18 19 import static com.android.cts.devicepolicy.TestCertificates.TEST_CA; 20 import static com.android.cts.devicepolicy.TestCertificates.TEST_CERT; 21 import static com.android.cts.devicepolicy.TestCertificates.TEST_KEY; 22 23 import static com.google.common.truth.Truth.assertThat; 24 import static com.google.common.truth.Truth.assertWithMessage; 25 26 import static org.testng.Assert.assertThrows; 27 28 import static java.util.Collections.singleton; 29 30 import android.app.admin.DevicePolicyManager; 31 import android.content.Context; 32 import android.content.pm.PackageManager; 33 import android.os.Build; 34 import android.os.Process; 35 import android.security.AttestedKeyPair; 36 import android.security.KeyChain; 37 import android.security.keystore.KeyGenParameterSpec; 38 import android.security.keystore.KeyProperties; 39 import android.telephony.TelephonyManager; 40 import android.test.InstrumentationTestCase; 41 import android.util.Base64; 42 import android.util.Base64InputStream; 43 44 import java.io.ByteArrayInputStream; 45 import java.io.IOException; 46 import java.security.GeneralSecurityException; 47 import java.security.KeyFactory; 48 import java.security.KeyStore; 49 import java.security.PrivateKey; 50 import java.security.cert.Certificate; 51 import java.security.cert.CertificateException; 52 import java.security.cert.CertificateFactory; 53 import java.security.spec.PKCS8EncodedKeySpec; 54 import java.util.List; 55 import java.util.Map; 56 57 /* 58 * Tests the delegated certificate installer functionality. 59 * 60 * This class is configured as DelegatedCertInstaller by the DelegatedCertInstallerHelper and is 61 * invoked directly from the host class, 62 * DeviceAndProfileOwnerTest#testDelegatedCertInstallerDirectly. 63 * 64 * TODO: this class is missing more functionality of the DelegatedCertInstaller tests. 65 * When this class is done then the DelegatedCertInstallerTest can be deleted. 66 */ 67 public class DirectDelegatedCertInstallerTest extends InstrumentationTestCase { 68 private static final String TEST_ALIAS = "DirectDelegatedCertInstallerTest-keypair"; 69 private static final String NON_EXISTENT_ALIAS = "DirectDelegatedCertInstallerTest-nonexistent"; 70 71 private DevicePolicyManager mDpm; 72 private PrivateKey mTestPrivateKey; 73 private Certificate mTestCertificate; 74 private boolean mHasTelephony = false; 75 private TelephonyManager mTelephonyManager; 76 77 @Override setUp()78 public void setUp() throws Exception { 79 super.setUp(); 80 mTestPrivateKey = rsaKeyFromString(TEST_KEY); 81 mTestCertificate = certificateFromString(TEST_CERT); 82 mDpm = getContext().getSystemService(DevicePolicyManager.class); 83 PackageManager pm = getContext().getPackageManager(); 84 if (pm != null && pm.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) { 85 mHasTelephony = true; 86 mTelephonyManager = (TelephonyManager) getContext().getSystemService( 87 Context.TELEPHONY_SERVICE); 88 } 89 } 90 91 @Override tearDown()92 public void tearDown() throws Exception { 93 mDpm.uninstallCaCert(null, TEST_CA.getBytes()); 94 mDpm.removeKeyPair(null, TEST_ALIAS); 95 super.tearDown(); 96 } 97 testCaCertsOperations()98 public void testCaCertsOperations() throws GeneralSecurityException, IOException { 99 final byte[] cert = TEST_CA.getBytes(); 100 final Certificate caCert = CertificateFactory.getInstance("X.509") 101 .generateCertificate(new ByteArrayInputStream(cert)); 102 103 // Exercise installCaCert() 104 KeyStore keyStore = KeyStore.getInstance("AndroidCAStore"); 105 keyStore.load(null, null); 106 assertWithMessage("CA certificate must not be installed in KeyStore at the" 107 + " beginning of the test").that(keyStore.getCertificateAlias(caCert)).isNull(); 108 assertWithMessage("CA certificate must not be installed in the DPM at the" 109 + " beginning of the test").that(mDpm.hasCaCertInstalled(null, cert)).isFalse(); 110 111 112 assertWithMessage("Expecting CA certificate installation to succeed").that( 113 mDpm.installCaCert(null, cert)).isTrue(); 114 assertWithMessage("Expecting CA cert to be installed").that( 115 mDpm.hasCaCertInstalled(null, cert)).isTrue(); 116 117 // Exercise getInstalledCaCerts() 118 assertWithMessage("Expecting CA cert to be in the list of installed CA certs").that( 119 containsCertificate(mDpm.getInstalledCaCerts(null), cert)).isTrue(); 120 121 // Verify that the CA cert was marked as installed by the Device Owner / Profile Owner. 122 assertWithMessage("CA cert should have a KeyStore alias").that( 123 keyStore.getCertificateAlias(caCert)).isNotNull(); 124 125 mDpm.uninstallCaCert(null, cert); 126 assertWithMessage("Expecting CA cert to no longer be installed").that( 127 mDpm.hasCaCertInstalled(null, cert)).isFalse(); 128 } 129 testInstallKeyPair()130 public void testInstallKeyPair() throws Exception { 131 final String alias = "delegated-cert-installer-test-key"; 132 133 assertThat(mDpm.installKeyPair(null, mTestPrivateKey, new Certificate[]{mTestCertificate}, 134 alias, true)).isTrue(); 135 136 // Test that the installed private key can be obtained. 137 PrivateKey obtainedKey = KeyChain.getPrivateKey(getContext(), alias); 138 assertThat(obtainedKey).isNotNull(); 139 assertThat(obtainedKey.getAlgorithm()).isEqualTo("RSA"); 140 141 // Test cleaning up the key. 142 assertThat(mDpm.removeKeyPair(null, alias)).isTrue(); 143 assertThat(KeyChain.getPrivateKey(getContext(), alias)).isNull(); 144 } 145 146 // Test that a key generation request succeeds when device identifiers are not requested. testGenerateKeyPairWithoutDeviceIdAttestation()147 public void testGenerateKeyPairWithoutDeviceIdAttestation() { 148 final String alias = "com.android.test.generated-rsa-1"; 149 try { 150 KeyGenParameterSpec keySpec = new KeyGenParameterSpec.Builder( 151 alias, 152 KeyProperties.PURPOSE_SIGN | KeyProperties.PURPOSE_VERIFY) 153 .setKeySize(2048) 154 .setDigests(KeyProperties.DIGEST_SHA256) 155 .setSignaturePaddings(KeyProperties.SIGNATURE_PADDING_RSA_PSS, 156 KeyProperties.SIGNATURE_PADDING_RSA_PKCS1) 157 .setIsStrongBoxBacked(false) 158 .setAttestationChallenge(new byte[]{'a', 'b', 'c'}) 159 .build(); 160 161 AttestedKeyPair generated = mDpm.generateKeyPair( 162 null, "RSA", keySpec, 0); 163 assertThat(generated).isNotNull(); 164 } finally { 165 assertThat(mDpm.removeKeyPair(null, alias)).isTrue(); 166 } 167 } 168 testAccessToDeviceIdentifiers()169 public void testAccessToDeviceIdentifiers() { 170 final String adminPackageName = "com.android.cts.deviceandprofileowner"; 171 if (mDpm.isDeviceOwnerApp(adminPackageName)) { 172 validateCanAccessDeviceIdentifiers(); 173 } else { 174 validateNoAccessToIdentifier(); 175 } 176 } 177 validateNoAccessToIdentifier()178 private void validateNoAccessToIdentifier() { 179 assertThrows(SecurityException.class, () -> Build.getSerial()); 180 181 if (!mHasTelephony) { 182 return; 183 } 184 185 assertWithMessage("Telephony service must be available.") 186 .that(mTelephonyManager).isNotNull(); 187 188 assertThrows(SecurityException.class, () -> mTelephonyManager.getImei()); 189 } 190 validateCanAccessDeviceIdentifiers()191 public void validateCanAccessDeviceIdentifiers() { 192 assertThat(Build.getSerial()).doesNotMatch(Build.UNKNOWN); 193 194 if (!mHasTelephony) { 195 return; 196 } 197 198 assertWithMessage("Telephony service must be available.") 199 .that(mTelephonyManager).isNotNull(); 200 201 try { 202 mTelephonyManager.getImei(); 203 } catch (SecurityException e) { 204 fail("Should have permission to access IMEI: " + e); 205 } 206 } 207 testHasKeyPair_NonExistent()208 public void testHasKeyPair_NonExistent() { 209 assertThat(mDpm.hasKeyPair(NON_EXISTENT_ALIAS)).isFalse(); 210 } 211 testHasKeyPair_Installed()212 public void testHasKeyPair_Installed() { 213 mDpm.installKeyPair(null, mTestPrivateKey, new Certificate[]{mTestCertificate}, TEST_ALIAS, 214 /* requestAccess= */ true); 215 216 assertThat(mDpm.hasKeyPair(TEST_ALIAS)).isTrue(); 217 } 218 testHasKeyPair_Removed()219 public void testHasKeyPair_Removed() { 220 mDpm.installKeyPair(null, mTestPrivateKey, new Certificate[]{mTestCertificate}, TEST_ALIAS, 221 /* requestAccess= */ true); 222 mDpm.removeKeyPair(null, TEST_ALIAS); 223 224 assertThat(mDpm.hasKeyPair(TEST_ALIAS)).isFalse(); 225 } 226 testGetKeyPairGrants_Empty()227 public void testGetKeyPairGrants_Empty() { 228 // Not granting upon install. 229 mDpm.installKeyPair(null, mTestPrivateKey, new Certificate[]{mTestCertificate}, TEST_ALIAS, 230 /* requestAccess= */ false); 231 232 assertThat(mDpm.getKeyPairGrants(TEST_ALIAS)).isEmpty(); 233 } 234 testGetKeyPairGrants_NonEmpty()235 public void testGetKeyPairGrants_NonEmpty() { 236 // Granting upon install. 237 mDpm.installKeyPair(null, mTestPrivateKey, new Certificate[]{mTestCertificate}, TEST_ALIAS, 238 /* requestAccess= */ true); 239 240 assertThat(mDpm.getKeyPairGrants(TEST_ALIAS)) 241 .isEqualTo(Map.of(Process.myUid(), singleton(getContext().getPackageName()))); 242 } 243 testIsWifiGrant_default()244 public void testIsWifiGrant_default() { 245 mDpm.installKeyPair(null, mTestPrivateKey, new Certificate[]{mTestCertificate}, 246 TEST_ALIAS, /* requestAccess= */ false); 247 248 assertThat(mDpm.isKeyPairGrantedToWifiAuth(TEST_ALIAS)).isFalse(); 249 } 250 testIsWifiGrant_allowed()251 public void testIsWifiGrant_allowed() { 252 mDpm.installKeyPair(null, mTestPrivateKey, new Certificate[]{mTestCertificate}, 253 TEST_ALIAS, /* requestAccess= */ false); 254 assertTrue(mDpm.grantKeyPairToWifiAuth(TEST_ALIAS)); 255 256 assertThat(mDpm.isKeyPairGrantedToWifiAuth(TEST_ALIAS)).isTrue(); 257 } 258 testIsWifiGrant_denied()259 public void testIsWifiGrant_denied() { 260 mDpm.installKeyPair(null, mTestPrivateKey, new Certificate[]{mTestCertificate}, 261 TEST_ALIAS, /* requestAccess= */ false); 262 assertTrue(mDpm.grantKeyPairToWifiAuth(TEST_ALIAS)); 263 assertTrue(mDpm.revokeKeyPairFromWifiAuth(TEST_ALIAS)); 264 265 assertThat(mDpm.isKeyPairGrantedToWifiAuth(TEST_ALIAS)).isFalse(); 266 } 267 rsaKeyFromString(String key)268 private PrivateKey rsaKeyFromString(String key) throws Exception { 269 final PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec( 270 Base64.decode(key, Base64.DEFAULT)); 271 return KeyFactory.getInstance("RSA").generatePrivate(keySpec); 272 } 273 certificateFromString(String cert)274 private Certificate certificateFromString(String cert) throws Exception { 275 return CertificateFactory.getInstance("X.509").generateCertificate( 276 new Base64InputStream(new ByteArrayInputStream(cert.getBytes()), Base64.DEFAULT)); 277 } 278 containsCertificate(List<byte[]> certificates, byte[] toMatch)279 private static boolean containsCertificate(List<byte[]> certificates, byte[] toMatch) 280 throws CertificateException { 281 Certificate certificateToMatch = readCertificate(toMatch); 282 for (byte[] certBuffer : certificates) { 283 Certificate cert = readCertificate(certBuffer); 284 if (certificateToMatch.equals(cert)) { 285 return true; 286 } 287 } 288 return false; 289 } 290 readCertificate(byte[] certBuffer)291 private static Certificate readCertificate(byte[] certBuffer) throws CertificateException { 292 final CertificateFactory certFactory = CertificateFactory.getInstance("X.509"); 293 return certFactory.generateCertificate(new ByteArrayInputStream(certBuffer)); 294 } 295 getContext()296 private Context getContext() { 297 return getInstrumentation().getContext(); 298 } 299 } 300