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