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.keychain;
18 
19 import static com.google.common.truth.Truth.assertThat;
20 
21 import static org.junit.Assert.assertFalse;
22 import static org.junit.Assert.assertTrue;
23 import static org.junit.Assert.fail;
24 import static org.mockito.ArgumentMatchers.any;
25 import static org.mockito.ArgumentMatchers.anyInt;
26 import static org.mockito.ArgumentMatchers.anyVararg;
27 import static org.mockito.Mockito.doReturn;
28 import static org.mockito.Mockito.doThrow;
29 import static org.mockito.Mockito.never;
30 import static org.mockito.Mockito.times;
31 import static org.mockito.Mockito.verify;
32 import static org.robolectric.Shadows.shadowOf;
33 
34 import android.app.admin.SecurityLog;
35 import android.content.Intent;
36 import android.content.pm.PackageManager;
37 import android.net.Uri;
38 import android.security.AppUriAuthenticationPolicy;
39 import android.security.IKeyChainService;
40 
41 import com.android.org.conscrypt.TrustedCertificateStore;
42 
43 import org.junit.Before;
44 import org.junit.Test;
45 import org.junit.runner.RunWith;
46 import org.mockito.Mock;
47 import org.mockito.MockitoAnnotations;
48 import org.robolectric.Robolectric;
49 import org.robolectric.RobolectricTestRunner;
50 import org.robolectric.RuntimeEnvironment;
51 import org.robolectric.android.controller.ServiceController;
52 import org.robolectric.annotation.Config;
53 import org.robolectric.shadows.ShadowPackageManager;
54 
55 import java.io.ByteArrayInputStream;
56 import java.io.IOException;
57 import java.security.KeyStore;
58 import java.security.cert.CertificateException;
59 import java.security.cert.CertificateFactory;
60 import java.security.cert.X509Certificate;
61 
62 import javax.security.auth.x500.X500Principal;
63 
64 @RunWith(RobolectricTestRunner.class)
65 @Config(shadows = {
66     ShadowTrustedCertificateStore.class,
67 })
68 public final class KeyChainServiceRoboTest {
69 
70     private static final String DEFAULT_KEYSTORE_TYPE = "BKS";
71 
72     private IKeyChainService.Stub mKeyChain;
73 
74     @Mock
75     private KeyChainService.Injector mockInjector;
76     @Mock
77     private TrustedCertificateStore mockCertStore;
78 
79     /*
80      * The CA cert below is the content of cacert.pem as generated by:
81      * openssl req -new -x509 -days 3650 -extensions v3_ca -keyout cakey.pem -out cacert.pem
82      */
83     private static final String TEST_CA =
84             "-----BEGIN CERTIFICATE-----\n" +
85             "MIIDXTCCAkWgAwIBAgIJAK9Tl/F9V8kSMA0GCSqGSIb3DQEBCwUAMEUxCzAJBgNV\n" +
86             "BAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBX\n" +
87             "aWRnaXRzIFB0eSBMdGQwHhcNMTUwMzA2MTczMjExWhcNMjUwMzAzMTczMjExWjBF\n" +
88             "MQswCQYDVQQGEwJBVTETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50\n" +
89             "ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB\n" +
90             "CgKCAQEAvItOutsE75WBTgTyNAHt4JXQ3JoseaGqcC3WQij6vhrleWi5KJ0jh1/M\n" +
91             "Rpry7Fajtwwb4t8VZa0NuM2h2YALv52w1xivql88zce/HU1y7XzbXhxis9o6SCI+\n" +
92             "oVQSbPeXRgBPppFzBEh3ZqYTVhAqw451XhwdA4Aqs3wts7ddjwlUzyMdU44osCUg\n" +
93             "kVg7lfPf9sTm5IoHVcfLSCWH5n6Nr9sH3o2ksyTwxuOAvsN11F/a0mmUoPciYPp+\n" +
94             "q7DzQzdi7akRG601DZ4YVOwo6UITGvDyuAAdxl5isovUXqe6Jmz2/myTSpAKxGFs\n" +
95             "jk9oRoG6WXWB1kni490GIPjJ1OceyQIDAQABo1AwTjAdBgNVHQ4EFgQUH1QIlPKL\n" +
96             "p2OQ/AoLOjKvBW4zK3AwHwYDVR0jBBgwFoAUH1QIlPKLp2OQ/AoLOjKvBW4zK3Aw\n" +
97             "DAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAcMi4voMMJHeQLjtq8Oky\n" +
98             "Azpyk8moDwgCd4llcGj7izOkIIFqq/lyqKdtykVKUWz2bSHO5cLrtaOCiBWVlaCV\n" +
99             "DYAnnVLM8aqaA6hJDIfaGs4zmwz0dY8hVMFCuCBiLWuPfiYtbEmjHGSmpQTG6Qxn\n" +
100             "ZJlaK5CZyt5pgh5EdNdvQmDEbKGmu0wpCq9qjZImwdyAul1t/B0DrsWApZMgZpeI\n" +
101             "d2od0VBrCICB1K4p+C51D93xyQiva7xQcCne+TAnGNy9+gjQ/MyR8MRpwRLv5ikD\n" +
102             "u0anJCN8pXo6IMglfMAsoton1J6o5/ae5uhC6caQU8bNUsCK570gpNfjkzo6rbP0\n" +
103             "wQ==\n" +
104             "-----END CERTIFICATE-----\n";
105 
106     private static final String NON_EXISTING_ALIAS = "alias-does-not-exist-1";
107 
108     private static final String TEST_PACKAGE_NAME_1 = "com.android.test";
109     private static final Uri TEST_URI_1 = Uri.parse("test.com");
110     private static final String TEST_ALIAS_1 = "testAlias";
111     private static final String CREDENTIAL_MANAGER_PACKAGE = "com.android.cred.mng.pkg";
112     private static final AppUriAuthenticationPolicy AUTHENTICATION_POLICY =
113             new AppUriAuthenticationPolicy.Builder()
114                     .addAppAndUriMapping(TEST_PACKAGE_NAME_1, TEST_URI_1, TEST_ALIAS_1)
115                     .build();
116 
117     private X509Certificate mCert;
118     private String mSubject;
119     private ShadowPackageManager mShadowPackageManager;
120 
121     @Before
setUp()122     public void setUp() throws Exception {
123         MockitoAnnotations.initMocks(this);
124         ShadowTrustedCertificateStore.sDelegate = mockCertStore;
125 
126         mCert = parseCertificate(TEST_CA);
127         mSubject = mCert.getSubjectX500Principal().getName(X500Principal.CANONICAL);
128 
129         final PackageManager packageManager = RuntimeEnvironment.application.getPackageManager();
130         mShadowPackageManager = shadowOf(packageManager);
131 
132         final ServiceController<KeyChainService> serviceController =
133                 Robolectric.buildService(KeyChainService.class);
134         final KeyChainService service = serviceController.get();
135         service.setInjector(mockInjector);
136         doReturn(KeyStore.getInstance(DEFAULT_KEYSTORE_TYPE))
137                 .when(mockInjector).getKeyStoreInstance();
138         serviceController.create().bind();
139 
140         final Intent intent = new Intent(IKeyChainService.class.getName());
141         mKeyChain = (IKeyChainService.Stub) service.onBind(intent);
142     }
143 
144     @Test
testCaInstallSuccessLogging()145     public void testCaInstallSuccessLogging() throws Exception {
146         setUpLoggingAndAccess(true);
147 
148         mKeyChain.installCaCertificate(TEST_CA.getBytes());
149 
150         verify(mockInjector, times(1)).writeSecurityEvent(
151                 SecurityLog.TAG_CERT_AUTHORITY_INSTALLED, 1 /* success */, mSubject, 0);
152     }
153 
154     @Test
testCaInstallFailedLogging()155     public void testCaInstallFailedLogging() throws Exception {
156         setUpLoggingAndAccess(true);
157 
158         doThrow(new IOException()).when(mockCertStore).installCertificate(any());
159 
160         try {
161             mKeyChain.installCaCertificate(TEST_CA.getBytes());
162             fail("didn't propagate the exception");
163         } catch (IllegalStateException expected) {
164             assertTrue(expected.getCause() instanceof IOException);
165         }
166 
167         verify(mockInjector, times(1)).writeSecurityEvent(
168                 SecurityLog.TAG_CERT_AUTHORITY_INSTALLED, 0 /* failure */, mSubject, 0);
169     }
170 
171     @Test
testCaRemoveSuccessLogging()172     public void testCaRemoveSuccessLogging() throws Exception {
173         setUpLoggingAndAccess(true);
174 
175         doReturn(mCert).when(mockCertStore).getCertificate("alias");
176 
177         mKeyChain.deleteCaCertificate("alias");
178 
179         verify(mockInjector, times(1)).writeSecurityEvent(
180                 SecurityLog.TAG_CERT_AUTHORITY_REMOVED, 1 /* success */, mSubject, 0);
181     }
182 
183     @Test
testCaRemoveFailedLogging()184     public void testCaRemoveFailedLogging() throws Exception {
185         setUpLoggingAndAccess(true);
186 
187         doReturn(mCert).when(mockCertStore).getCertificate("alias");
188         doThrow(new IOException()).when(mockCertStore).deleteCertificateEntry(any());
189 
190         mKeyChain.deleteCaCertificate("alias");
191 
192         verify(mockInjector, times(1)).writeSecurityEvent(
193                 SecurityLog.TAG_CERT_AUTHORITY_REMOVED, 0 /* failure */, mSubject, 0);
194     }
195 
196     @Test
testNoLoggingWhenDisabled()197     public void testNoLoggingWhenDisabled() throws Exception {
198         setUpLoggingAndAccess(false);
199 
200         doReturn(mCert).when(mockCertStore).getCertificate("alias");
201 
202         mKeyChain.installCaCertificate(TEST_CA.getBytes());
203         mKeyChain.deleteCaCertificate("alias");
204 
205         doThrow(new IOException()).when(mockCertStore).installCertificate(any());
206         doThrow(new IOException()).when(mockCertStore).deleteCertificateEntry(any());
207 
208         try {
209             mKeyChain.installCaCertificate(TEST_CA.getBytes());
210             fail("didn't propagate the exception");
211         } catch (IllegalStateException expected) {
212             assertTrue(expected.getCause() instanceof IOException);
213         }
214         mKeyChain.deleteCaCertificate("alias");
215 
216         verify(mockInjector, never()).writeSecurityEvent(anyInt(), anyInt(), anyVararg());
217     }
218 
parseCertificate(String cert)219     private X509Certificate parseCertificate(String cert) throws CertificateException {
220         final CertificateFactory cf = CertificateFactory.getInstance("X.509");
221         return (X509Certificate) cf.generateCertificate(new ByteArrayInputStream(cert.getBytes()));
222     }
223 
224     @Test
testBadPackagesNotAllowedToInstallCaCerts()225     public void testBadPackagesNotAllowedToInstallCaCerts() throws Exception {
226         setUpCaller(1000666, null);
227         try {
228             mKeyChain.installCaCertificate(TEST_CA.getBytes());
229             fail("didn't throw the exception");
230         } catch (SecurityException expected) {}
231     }
232 
233     @Test
testNonSystemPackagesNotAllowedToInstallCaCerts()234     public void testNonSystemPackagesNotAllowedToInstallCaCerts()  throws Exception {
235         setUpCaller(1000666, "xxx.nasty.flashlight");
236         try {
237             mKeyChain.installCaCertificate(TEST_CA.getBytes());
238             fail("didn't throw the exception");
239         } catch (SecurityException expected) {}
240     }
241 
242     @Test
testRequestPrivateKeyReturnsNullForNonExistingAlias()243     public void testRequestPrivateKeyReturnsNullForNonExistingAlias() throws Exception {
244         String privateKey = mKeyChain.requestPrivateKey(NON_EXISTING_ALIAS);
245         assertThat(privateKey).isNull();
246     }
247 
248     @Test
testGetCertificateReturnsNullForNonExistingAlias()249     public void testGetCertificateReturnsNullForNonExistingAlias() throws Exception {
250         byte[] certificate = mKeyChain.getCertificate(NON_EXISTING_ALIAS);
251         assertThat(certificate).isNull();
252     }
253 
254     @Test
testGetCaCertificatesReturnsNullForNonExistingAlias()255     public void testGetCaCertificatesReturnsNullForNonExistingAlias() throws Exception {
256         byte[] certificate = mKeyChain.getCaCertificates(NON_EXISTING_ALIAS);
257         assertThat(certificate).isNull();
258     }
259 
260     @Test
testHasCredentialManagementApp_noManagementApp_returnsFalse()261     public void testHasCredentialManagementApp_noManagementApp_returnsFalse() throws Exception {
262         setUpSystemCaller();
263         assertFalse(mKeyChain.hasCredentialManagementApp());
264     }
265 
266     @Test
testGetCredentialManagementAppPackageName_noManagementApp_returnsNull()267     public void testGetCredentialManagementAppPackageName_noManagementApp_returnsNull()
268             throws Exception {
269         setUpSystemCaller();
270         assertThat(mKeyChain.getCredentialManagementAppPackageName()).isNull();
271     }
272 
273     @Test
testGetCredentialManagementAppPolicy_noManagementApp_returnsNull()274     public void testGetCredentialManagementAppPolicy_noManagementApp_returnsNull()
275             throws Exception {
276         setUpSystemCaller();
277         assertThat(mKeyChain.getCredentialManagementAppPolicy()).isNull();
278     }
279 
280     @Test
testGetPredefinedAliasForPackageAndUri_noManagementApp_returnsNull()281     public void testGetPredefinedAliasForPackageAndUri_noManagementApp_returnsNull()
282             throws Exception {
283         setUpSystemCaller();
284         assertThat(mKeyChain.getPredefinedAliasForPackageAndUri(TEST_PACKAGE_NAME_1,
285                 TEST_URI_1)).isNull();
286     }
287 
288     @Test
testHasCredentialManagement_hasManagementApp_returnsTrue()289     public void testHasCredentialManagement_hasManagementApp_returnsTrue() throws Exception {
290         setUpSystemCaller();
291         mKeyChain.setCredentialManagementApp(CREDENTIAL_MANAGER_PACKAGE, AUTHENTICATION_POLICY);
292 
293         assertTrue(mKeyChain.hasCredentialManagementApp());
294     }
295 
296     @Test
testGetCredentialManagementAppPackageName_hasManagementApp_returnsPackageName()297     public void testGetCredentialManagementAppPackageName_hasManagementApp_returnsPackageName()
298             throws Exception {
299         setUpSystemCaller();
300         mKeyChain.setCredentialManagementApp(CREDENTIAL_MANAGER_PACKAGE, AUTHENTICATION_POLICY);
301 
302         assertThat(mKeyChain.getCredentialManagementAppPackageName())
303                 .isEqualTo(CREDENTIAL_MANAGER_PACKAGE);
304     }
305 
306     @Test
testGetCredentialManagementAppPolicy_hasManagementApp_returnsPolicy()307     public void testGetCredentialManagementAppPolicy_hasManagementApp_returnsPolicy()
308             throws Exception {
309         setUpSystemCaller();
310         mKeyChain.setCredentialManagementApp(CREDENTIAL_MANAGER_PACKAGE, AUTHENTICATION_POLICY);
311 
312         assertThat(mKeyChain.getCredentialManagementAppPolicy()).isEqualTo(AUTHENTICATION_POLICY);
313     }
314 
315     @Test
testGetPredefinedAliasForPackageAndUri_hasManagementApp_returnsCorrectAlias()316     public void testGetPredefinedAliasForPackageAndUri_hasManagementApp_returnsCorrectAlias()
317             throws Exception {
318         setUpSystemCaller();
319         mKeyChain.setCredentialManagementApp(CREDENTIAL_MANAGER_PACKAGE, AUTHENTICATION_POLICY);
320 
321         assertThat(mKeyChain.getPredefinedAliasForPackageAndUri(TEST_PACKAGE_NAME_1, TEST_URI_1))
322                 .isEqualTo(TEST_ALIAS_1);
323     }
324 
325     @Test
testRemoveCredentialManagementApp_hasManagementApp_removesManagementApp()326     public void testRemoveCredentialManagementApp_hasManagementApp_removesManagementApp()
327             throws Exception {
328         setUpSystemCaller();
329 
330         mKeyChain.removeCredentialManagementApp();
331 
332         assertFalse(mKeyChain.hasCredentialManagementApp());
333         assertThat(mKeyChain.getCredentialManagementAppPackageName()).isNull();
334         assertThat(mKeyChain.getCredentialManagementAppPolicy()).isNull();
335     }
336 
setUpLoggingAndAccess(boolean loggingEnabled)337     private void setUpLoggingAndAccess(boolean loggingEnabled) {
338         doReturn(loggingEnabled).when(mockInjector).isSecurityLoggingEnabled();
339 
340         // Pretend that the caller is system.
341         setUpCaller(1000, "android.uid.system:1000");
342     }
343 
setUpSystemCaller()344     private void setUpSystemCaller() {
345         setUpCaller(1000, "android.uid.system:1000");
346     }
347 
setUpCaller(int uid, String packageName)348     private void setUpCaller(int uid, String packageName) {
349         doReturn(uid).when(mockInjector).getCallingUid();
350         mShadowPackageManager.setNameForUid(uid, packageName);
351     }
352 }
353