• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 org.junit.Assert.assertTrue;
20 import static org.junit.Assert.fail;
21 import static org.mockito.ArgumentMatchers.any;
22 import static org.mockito.ArgumentMatchers.anyInt;
23 import static org.mockito.ArgumentMatchers.anyVararg;
24 import static org.mockito.Mockito.doReturn;
25 import static org.mockito.Mockito.doThrow;
26 import static org.mockito.Mockito.never;
27 import static org.mockito.Mockito.times;
28 import static org.mockito.Mockito.verify;
29 import static org.robolectric.Shadows.shadowOf;
30 
31 import android.app.admin.SecurityLog;
32 import android.content.Intent;
33 import android.content.pm.PackageManager;
34 import android.security.IKeyChainService;
35 
36 import com.android.org.conscrypt.TrustedCertificateStore;
37 
38 import org.junit.Before;
39 import org.junit.Test;
40 import org.junit.runner.RunWith;
41 import org.mockito.Mock;
42 import org.mockito.MockitoAnnotations;
43 import org.robolectric.Robolectric;
44 import org.robolectric.RobolectricTestRunner;
45 import org.robolectric.RuntimeEnvironment;
46 import org.robolectric.android.controller.ServiceController;
47 import org.robolectric.annotation.Config;
48 import org.robolectric.shadows.ShadowPackageManager;
49 
50 import java.io.ByteArrayInputStream;
51 import java.io.IOException;
52 import java.security.cert.CertificateException;
53 import java.security.cert.CertificateFactory;
54 import java.security.cert.X509Certificate;
55 
56 import javax.security.auth.x500.X500Principal;
57 
58 @RunWith(RobolectricTestRunner.class)
59 @Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION,
60         shadows = {
61                 ShadowTrustedCertificateStore.class,
62                 ShadowPackageManager.class,
63         })
64 public final class KeyChainServiceRoboTest {
65     private IKeyChainService.Stub mKeyChain;
66 
67     @Mock
68     private KeyChainService.Injector mockInjector;
69     @Mock
70     private TrustedCertificateStore mockCertStore;
71 
72     /*
73      * The CA cert below is the content of cacert.pem as generated by:
74      * openssl req -new -x509 -days 3650 -extensions v3_ca -keyout cakey.pem -out cacert.pem
75      */
76     private static final String TEST_CA =
77             "-----BEGIN CERTIFICATE-----\n" +
78             "MIIDXTCCAkWgAwIBAgIJAK9Tl/F9V8kSMA0GCSqGSIb3DQEBCwUAMEUxCzAJBgNV\n" +
79             "BAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBX\n" +
80             "aWRnaXRzIFB0eSBMdGQwHhcNMTUwMzA2MTczMjExWhcNMjUwMzAzMTczMjExWjBF\n" +
81             "MQswCQYDVQQGEwJBVTETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50\n" +
82             "ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB\n" +
83             "CgKCAQEAvItOutsE75WBTgTyNAHt4JXQ3JoseaGqcC3WQij6vhrleWi5KJ0jh1/M\n" +
84             "Rpry7Fajtwwb4t8VZa0NuM2h2YALv52w1xivql88zce/HU1y7XzbXhxis9o6SCI+\n" +
85             "oVQSbPeXRgBPppFzBEh3ZqYTVhAqw451XhwdA4Aqs3wts7ddjwlUzyMdU44osCUg\n" +
86             "kVg7lfPf9sTm5IoHVcfLSCWH5n6Nr9sH3o2ksyTwxuOAvsN11F/a0mmUoPciYPp+\n" +
87             "q7DzQzdi7akRG601DZ4YVOwo6UITGvDyuAAdxl5isovUXqe6Jmz2/myTSpAKxGFs\n" +
88             "jk9oRoG6WXWB1kni490GIPjJ1OceyQIDAQABo1AwTjAdBgNVHQ4EFgQUH1QIlPKL\n" +
89             "p2OQ/AoLOjKvBW4zK3AwHwYDVR0jBBgwFoAUH1QIlPKLp2OQ/AoLOjKvBW4zK3Aw\n" +
90             "DAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAcMi4voMMJHeQLjtq8Oky\n" +
91             "Azpyk8moDwgCd4llcGj7izOkIIFqq/lyqKdtykVKUWz2bSHO5cLrtaOCiBWVlaCV\n" +
92             "DYAnnVLM8aqaA6hJDIfaGs4zmwz0dY8hVMFCuCBiLWuPfiYtbEmjHGSmpQTG6Qxn\n" +
93             "ZJlaK5CZyt5pgh5EdNdvQmDEbKGmu0wpCq9qjZImwdyAul1t/B0DrsWApZMgZpeI\n" +
94             "d2od0VBrCICB1K4p+C51D93xyQiva7xQcCne+TAnGNy9+gjQ/MyR8MRpwRLv5ikD\n" +
95             "u0anJCN8pXo6IMglfMAsoton1J6o5/ae5uhC6caQU8bNUsCK570gpNfjkzo6rbP0\n" +
96             "wQ==\n" +
97             "-----END CERTIFICATE-----\n";
98 
99     private X509Certificate mCert;
100     private String mSubject;
101     private ShadowPackageManager mShadowPackageManager;
102 
103     @Before
setUp()104     public void setUp() throws Exception {
105         MockitoAnnotations.initMocks(this);
106         ShadowTrustedCertificateStore.sDelegate = mockCertStore;
107 
108         mCert = parseCertificate(TEST_CA);
109         mSubject = mCert.getSubjectX500Principal().getName(X500Principal.CANONICAL);
110 
111         final PackageManager packageManager = RuntimeEnvironment.application.getPackageManager();
112         mShadowPackageManager = shadowOf(packageManager);
113 
114         final ServiceController<KeyChainService> serviceController =
115                 Robolectric.buildService(KeyChainService.class).create().bind();
116         final KeyChainService service = serviceController.get();
117         service.setInjector(mockInjector);
118         final Intent intent = new Intent(IKeyChainService.class.getName());
119         mKeyChain = (IKeyChainService.Stub) service.onBind(intent);
120     }
121 
122     @Test
testCaInstallSuccessLogging()123     public void testCaInstallSuccessLogging() throws Exception {
124         setUpLoggingAndAccess(true);
125 
126         mKeyChain.installCaCertificate(TEST_CA.getBytes());
127 
128         verify(mockInjector, times(1)).writeSecurityEvent(
129                 SecurityLog.TAG_CERT_AUTHORITY_INSTALLED, 1 /* success */, mSubject);
130     }
131 
132     @Test
testCaInstallFailedLogging()133     public void testCaInstallFailedLogging() throws Exception {
134         setUpLoggingAndAccess(true);
135 
136         doThrow(new IOException()).when(mockCertStore).installCertificate(any());
137 
138         try {
139             mKeyChain.installCaCertificate(TEST_CA.getBytes());
140             fail("didn't propagate the exception");
141         } catch (IllegalStateException ignored) {
142             assertTrue(ignored.getCause() instanceof IOException);
143         }
144 
145         verify(mockInjector, times(1)).writeSecurityEvent(
146                 SecurityLog.TAG_CERT_AUTHORITY_INSTALLED, 0 /* failure */, mSubject);
147     }
148 
149     @Test
testCaRemoveSuccessLogging()150     public void testCaRemoveSuccessLogging() throws Exception {
151         setUpLoggingAndAccess(true);
152 
153         doReturn(mCert).when(mockCertStore).getCertificate("alias");
154 
155         mKeyChain.deleteCaCertificate("alias");
156 
157         verify(mockInjector, times(1)).writeSecurityEvent(
158                 SecurityLog.TAG_CERT_AUTHORITY_REMOVED, 1 /* success */, mSubject);
159     }
160 
161     @Test
testCaRemoveFailedLogging()162     public void testCaRemoveFailedLogging() throws Exception {
163         setUpLoggingAndAccess(true);
164 
165         doReturn(mCert).when(mockCertStore).getCertificate("alias");
166         doThrow(new IOException()).when(mockCertStore).deleteCertificateEntry(any());
167 
168         mKeyChain.deleteCaCertificate("alias");
169 
170         verify(mockInjector, times(1)).writeSecurityEvent(
171                 SecurityLog.TAG_CERT_AUTHORITY_REMOVED, 0 /* failure */, mSubject);
172     }
173 
174     @Test
testNoLoggingWhenDisabled()175     public void testNoLoggingWhenDisabled() throws Exception {
176         setUpLoggingAndAccess(false);
177 
178         doReturn(mCert).when(mockCertStore).getCertificate("alias");
179 
180         mKeyChain.installCaCertificate(TEST_CA.getBytes());
181         mKeyChain.deleteCaCertificate("alias");
182 
183         doThrow(new IOException()).when(mockCertStore).installCertificate(any());
184         doThrow(new IOException()).when(mockCertStore).deleteCertificateEntry(any());
185 
186         try {
187             mKeyChain.installCaCertificate(TEST_CA.getBytes());
188             fail("didn't propagate the exception");
189         } catch (IllegalStateException ignored) {
190             assertTrue(ignored.getCause() instanceof IOException);
191         }
192         mKeyChain.deleteCaCertificate("alias");
193 
194         verify(mockInjector, never()).writeSecurityEvent(anyInt(), anyInt(), anyVararg());
195     }
196 
parseCertificate(String cert)197     private X509Certificate parseCertificate(String cert) throws CertificateException {
198         final CertificateFactory cf = CertificateFactory.getInstance("X.509");
199         return (X509Certificate) cf.generateCertificate(new ByteArrayInputStream(cert.getBytes()));
200     }
201 
202     @Test
testBadPackagesNotAllowedToInstallCaCerts()203     public void testBadPackagesNotAllowedToInstallCaCerts() throws Exception {
204         setUpCaller(1000666, null);
205         try {
206             mKeyChain.installCaCertificate(TEST_CA.getBytes());
207             fail("didn't throw the exception");
208         } catch (SecurityException expected) {}
209     }
210 
211     @Test
testNonSystemPackagesNotAllowedToInstallCaCerts()212     public void testNonSystemPackagesNotAllowedToInstallCaCerts()  throws Exception {
213         setUpCaller(1000666, "xxx.nasty.flashlight");
214         try {
215             mKeyChain.installCaCertificate(TEST_CA.getBytes());
216             fail("didn't throw the exception");
217         } catch (SecurityException expected) {}
218     }
219 
setUpLoggingAndAccess(boolean loggingEnabled)220     private void setUpLoggingAndAccess(boolean loggingEnabled) {
221         doReturn(loggingEnabled).when(mockInjector).isSecurityLoggingEnabled();
222 
223         // Pretend that the caller is system.
224         setUpCaller(1000, "android.uid.system:1000");
225     }
226 
setUpCaller(int uid, String packageName)227     private void setUpCaller(int uid, String packageName) {
228         doReturn(uid).when(mockInjector).getCallingUid();
229         mShadowPackageManager.setNameForUid(uid, packageName);
230     }
231 }
232