1 /*
2  * Copyright (C) 2014 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 package com.android.cts.deviceowner;
17 
18 import static com.android.cts.deviceowner.BaseDeviceOwnerTest.getWho;
19 import static com.android.cts.deviceowner.FakeKeys.FAKE_RSA_1;
20 
21 import android.app.Activity;
22 import android.app.admin.DevicePolicyManager;
23 import android.net.Uri;
24 import android.security.KeyChain;
25 import android.security.KeyChainAliasCallback;
26 import android.security.KeyChainException;
27 import android.test.ActivityInstrumentationTestCase2;
28 
29 import java.io.ByteArrayInputStream;
30 import java.io.ByteArrayOutputStream;
31 import java.io.DataInputStream;
32 import java.io.IOException;
33 import java.io.InputStream;
34 import java.io.UnsupportedEncodingException;
35 import java.net.URLEncoder;
36 import java.security.cert.CertificateException;
37 import java.security.cert.CertificateFactory;
38 import java.security.cert.X509Certificate;
39 import java.security.cert.Certificate;
40 import java.security.KeyFactory;
41 import java.security.NoSuchAlgorithmException;
42 import java.security.PrivateKey;
43 import java.security.spec.InvalidKeySpecException;
44 import java.security.spec.PKCS8EncodedKeySpec;
45 import java.util.Arrays;
46 import java.util.Collection;
47 import java.util.concurrent.CountDownLatch;
48 import java.util.concurrent.TimeUnit;
49 
50 import android.content.ComponentName;
51 import android.content.Context;
52 import android.content.res.AssetManager;
53 
54 public class KeyManagementTest extends ActivityInstrumentationTestCase2<KeyManagementActivity> {
55 
56     private static final long KEYCHAIN_TIMEOUT_MINS = 6;
57     private DevicePolicyManager mDevicePolicyManager;
58 
KeyManagementTest()59     public KeyManagementTest() {
60         super(KeyManagementActivity.class);
61     }
62 
63     @Override
setUp()64     protected void setUp() throws Exception {
65         super.setUp();
66 
67         // Confirm our DeviceOwner is set up
68         mDevicePolicyManager = (DevicePolicyManager)
69                 getActivity().getSystemService(Context.DEVICE_POLICY_SERVICE);
70         BaseDeviceOwnerTest.assertDeviceOwner(mDevicePolicyManager);
71 
72         // Enable credential storage by setting a nonempty password.
73         assertTrue(mDevicePolicyManager.resetPassword("test", 0));
74     }
75 
76     @Override
tearDown()77     protected void tearDown() throws Exception {
78         // Delete all keys by resetting our password to null, which clears the keystore.
79         mDevicePolicyManager.setPasswordQuality(getWho(),
80                 DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED);
81         mDevicePolicyManager.setPasswordMinimumLength(getWho(), 0);
82         assertTrue(mDevicePolicyManager.resetPassword("", 0));
83         super.tearDown();
84     }
85 
testCanInstallAndRemoveValidRsaKeypair()86     public void testCanInstallAndRemoveValidRsaKeypair() throws Exception {
87         final String alias = "com.android.test.valid-rsa-key-1";
88         final PrivateKey privKey = getPrivateKey(FAKE_RSA_1.privateKey , "RSA");
89         final Certificate cert = getCertificate(FAKE_RSA_1.caCertificate);
90 
91         // Install keypair.
92         assertTrue(mDevicePolicyManager.installKeyPair(getWho(), privKey, cert, alias));
93         try {
94             // Request and retrieve using the alias.
95             assertGranted(alias, false);
96             assertEquals(alias, new KeyChainAliasFuture(alias).get());
97             assertGranted(alias, true);
98 
99             // Verify key is at least something like the one we put in.
100             assertEquals(KeyChain.getPrivateKey(getActivity(), alias).getAlgorithm(), "RSA");
101         } finally {
102             // Delete regardless of whether the test succeeded.
103             assertTrue(mDevicePolicyManager.removeKeyPair(getWho(), alias));
104         }
105         // Verify alias is actually deleted.
106         assertGranted(alias, false);
107     }
108 
testCanInstallWithAutomaticAccess()109     public void testCanInstallWithAutomaticAccess() throws Exception {
110         final String grant = "com.android.test.autogrant-key-1";
111         final String withhold = "com.android.test.nongrant-key-1";
112         final PrivateKey privKey = getPrivateKey(FAKE_RSA_1.privateKey , "RSA");
113         final Certificate cert = getCertificate(FAKE_RSA_1.caCertificate);
114 
115         // Install keypairs.
116         assertTrue(mDevicePolicyManager.installKeyPair(getWho(), privKey, new Certificate[] {cert},
117                 grant, true));
118         assertTrue(mDevicePolicyManager.installKeyPair(getWho(), privKey, new Certificate[] {cert},
119                 withhold, false));
120         try {
121             // Verify only the requested key was actually granted.
122             assertGranted(grant, true);
123             assertGranted(withhold, false);
124 
125             // Verify the granted key is actually obtainable in PrivateKey form.
126             assertEquals(KeyChain.getPrivateKey(getActivity(), grant).getAlgorithm(), "RSA");
127         } finally {
128             // Delete both keypairs.
129             assertTrue(mDevicePolicyManager.removeKeyPair(getWho(), grant));
130             assertTrue(mDevicePolicyManager.removeKeyPair(getWho(), withhold));
131         }
132         // Verify they're actually gone.
133         assertGranted(grant, false);
134         assertGranted(withhold, false);
135     }
136 
testCanInstallCertChain()137     public void testCanInstallCertChain() throws Exception {
138         // Use assets/generate-client-cert-chain.sh to regenerate the client cert chain.
139         final PrivateKey privKey = loadPrivateKeyFromAsset("user-cert-chain.key");
140         final Collection<Certificate> certs = loadCertificatesFromAsset("user-cert-chain.crt");
141         final Certificate[] certChain = certs.toArray(new Certificate[certs.size()]);
142         final String alias = "com.android.test.clientkeychain";
143         // Some sanity check on the cert chain
144         assertTrue(certs.size() > 1);
145         for (int i = 1; i < certs.size(); i++) {
146             certChain[i - 1].verify(certChain[i].getPublicKey());
147         }
148 
149         // Install keypairs.
150         assertTrue(mDevicePolicyManager.installKeyPair(getWho(), privKey, certChain, alias, true));
151         try {
152             // Verify only the requested key was actually granted.
153             assertGranted(alias, true);
154 
155             // Verify the granted key is actually obtainable in PrivateKey form.
156             assertEquals(KeyChain.getPrivateKey(getActivity(), alias).getAlgorithm(), "RSA");
157 
158             // Verify the certificate chain is correct
159             X509Certificate[] returnedCerts = KeyChain.getCertificateChain(getActivity(), alias);
160             assertTrue(Arrays.equals(certChain, returnedCerts));
161         } finally {
162             // Delete both keypairs.
163             assertTrue(mDevicePolicyManager.removeKeyPair(getWho(), alias));
164         }
165         // Verify they're actually gone.
166         assertGranted(alias, false);
167     }
168 
testGrantsDoNotPersistBetweenInstallations()169     public void testGrantsDoNotPersistBetweenInstallations() throws Exception {
170         final String alias = "com.android.test.persistent-key-1";
171         final PrivateKey privKey = getPrivateKey(FAKE_RSA_1.privateKey , "RSA");
172         final Certificate cert = getCertificate(FAKE_RSA_1.caCertificate);
173 
174         // Install keypair.
175         assertTrue(mDevicePolicyManager.installKeyPair(getWho(), privKey, new Certificate[] {cert},
176                 alias, true));
177         try {
178             assertGranted(alias, true);
179         } finally {
180             // Delete and verify.
181             assertTrue(mDevicePolicyManager.removeKeyPair(getWho(), alias));
182         }
183         assertGranted(alias, false);
184 
185         // Install again.
186         assertTrue(mDevicePolicyManager.installKeyPair(getWho(), privKey, new Certificate[] {cert},
187                 alias, false));
188         try {
189             assertGranted(alias, false);
190         } finally {
191             // Delete.
192             assertTrue(mDevicePolicyManager.removeKeyPair(getWho(), alias));
193         }
194     }
195 
testNullKeyParamsFailPredictably()196     public void testNullKeyParamsFailPredictably() throws Exception {
197         final String alias = "com.android.test.null-key-1";
198         final PrivateKey privKey = getPrivateKey(FAKE_RSA_1.privateKey, "RSA");
199         final Certificate cert = getCertificate(FAKE_RSA_1.caCertificate);
200         try {
201             mDevicePolicyManager.installKeyPair(getWho(), null, cert, alias);
202             fail("Exception should have been thrown for null PrivateKey");
203         } catch (NullPointerException expected) {
204         }
205         try {
206             mDevicePolicyManager.installKeyPair(getWho(), privKey, null, alias);
207             fail("Exception should have been thrown for null Certificate");
208         } catch (NullPointerException expected) {
209         }
210     }
211 
testNullAdminComponentIsDenied()212     public void testNullAdminComponentIsDenied() throws Exception {
213         final String alias = "com.android.test.null-admin-1";
214         final PrivateKey privKey = getPrivateKey(FAKE_RSA_1.privateKey, "RSA");
215         final Certificate cert = getCertificate(FAKE_RSA_1.caCertificate);
216         try {
217             mDevicePolicyManager.installKeyPair(null, privKey, cert, alias);
218             fail("Exception should have been thrown for null ComponentName");
219         } catch (SecurityException expected) {
220         }
221     }
222 
assertGranted(String alias, boolean expected)223     private void assertGranted(String alias, boolean expected) throws InterruptedException {
224         boolean granted = false;
225         try {
226             granted = (KeyChain.getPrivateKey(getActivity(), alias) != null);
227         } catch (KeyChainException e) {
228         }
229         assertEquals("Grant for alias: \"" + alias + "\"", expected, granted);
230     }
231 
getPrivateKey(final byte[] key, String type)232     private static PrivateKey getPrivateKey(final byte[] key, String type)
233             throws NoSuchAlgorithmException, InvalidKeySpecException {
234         return KeyFactory.getInstance(type).generatePrivate(
235                 new PKCS8EncodedKeySpec(key));
236     }
237 
getCertificate(byte[] cert)238     private static Certificate getCertificate(byte[] cert) throws CertificateException {
239         return CertificateFactory.getInstance("X.509").generateCertificate(
240                 new ByteArrayInputStream(cert));
241     }
242 
loadCertificatesFromAsset(String assetName)243     private Collection<Certificate> loadCertificatesFromAsset(String assetName) {
244         try {
245             final CertificateFactory certFactory = CertificateFactory.getInstance("X.509");
246             AssetManager am = getActivity().getAssets();
247             InputStream is = am.open(assetName);
248             return (Collection<Certificate>) certFactory.generateCertificates(is);
249         } catch (IOException | CertificateException e) {
250             e.printStackTrace();
251         }
252         return null;
253     }
254 
loadPrivateKeyFromAsset(String assetName)255     private PrivateKey loadPrivateKeyFromAsset(String assetName) {
256         try {
257             AssetManager am = getActivity().getAssets();
258             InputStream is = am.open(assetName);
259             ByteArrayOutputStream output = new ByteArrayOutputStream();
260             int length;
261             byte[] buffer = new byte[4096];
262             while ((length = is.read(buffer, 0, buffer.length)) != -1) {
263               output.write(buffer, 0, length);
264             }
265             return getPrivateKey(output.toByteArray(), "RSA");
266         } catch (IOException | NoSuchAlgorithmException | InvalidKeySpecException e) {
267             e.printStackTrace();
268         }
269         return null;
270     }
271 
272     private class KeyChainAliasFuture implements KeyChainAliasCallback {
273         private final CountDownLatch mLatch = new CountDownLatch(1);
274         private String mChosenAlias = null;
275 
276         @Override
alias(final String chosenAlias)277         public void alias(final String chosenAlias) {
278             mChosenAlias = chosenAlias;
279             mLatch.countDown();
280         }
281 
KeyChainAliasFuture(String alias)282         public KeyChainAliasFuture(String alias) throws UnsupportedEncodingException {
283             /* Pass the alias as a GET to an imaginary server instead of explicitly asking for it,
284              * to make sure the DPC actually has to do some work to grant the cert.
285              */
286             final Uri uri =
287                     Uri.parse("https://example.org/?alias=" + URLEncoder.encode(alias, "UTF-8"));
288             KeyChain.choosePrivateKeyAlias(getActivity(), this,
289                     null /* keyTypes */, null /* issuers */, uri, null /* alias */);
290         }
291 
get()292         public String get() throws InterruptedException {
293             assertTrue("Chooser timeout", mLatch.await(KEYCHAIN_TIMEOUT_MINS, TimeUnit.MINUTES));
294             return mChosenAlias;
295         }
296     }
297 }
298