blob: 147f1ca46e7f498b65f4805b50c3e6003b3e11a7 [file] [log] [blame]
Pavel Grafov6e0960b2018-01-19 12:27:35 +00001/*
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
17package com.android.keychain;
18
19import static org.junit.Assert.assertTrue;
20import static org.junit.Assert.fail;
21import static org.mockito.ArgumentMatchers.any;
22import static org.mockito.ArgumentMatchers.anyInt;
23import static org.mockito.ArgumentMatchers.anyVararg;
24import static org.mockito.Mockito.doReturn;
25import static org.mockito.Mockito.doThrow;
26import static org.mockito.Mockito.never;
27import static org.mockito.Mockito.times;
28import static org.mockito.Mockito.verify;
Pavel Grafov8b7af342018-02-01 18:48:24 +000029import static org.robolectric.Shadows.shadowOf;
Pavel Grafov6e0960b2018-01-19 12:27:35 +000030
31import android.app.admin.SecurityLog;
32import android.content.Intent;
Pavel Grafov8b7af342018-02-01 18:48:24 +000033import android.content.pm.PackageManager;
Pavel Grafov6e0960b2018-01-19 12:27:35 +000034import android.security.IKeyChainService;
35
36import com.android.org.conscrypt.TrustedCertificateStore;
37
38import org.junit.Before;
39import org.junit.Test;
40import org.junit.runner.RunWith;
41import org.mockito.Mock;
42import org.mockito.MockitoAnnotations;
43import org.robolectric.Robolectric;
44import org.robolectric.RobolectricTestRunner;
Pavel Grafov8b7af342018-02-01 18:48:24 +000045import org.robolectric.RuntimeEnvironment;
Pavel Grafov6e0960b2018-01-19 12:27:35 +000046import org.robolectric.android.controller.ServiceController;
47import org.robolectric.annotation.Config;
Pavel Grafov8b7af342018-02-01 18:48:24 +000048import org.robolectric.shadows.ShadowPackageManager;
Pavel Grafov6e0960b2018-01-19 12:27:35 +000049
50import java.io.ByteArrayInputStream;
51import java.io.IOException;
52import java.security.cert.CertificateException;
53import java.security.cert.CertificateFactory;
54import java.security.cert.X509Certificate;
55
56import javax.security.auth.x500.X500Principal;
57
58@RunWith(RobolectricTestRunner.class)
59@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION,
Pavel Grafov8b7af342018-02-01 18:48:24 +000060 shadows = {
61 ShadowTrustedCertificateStore.class,
62 ShadowPackageManager.class,
63 })
Pavel Grafov6e0960b2018-01-19 12:27:35 +000064public 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;
Pavel Grafov8b7af342018-02-01 18:48:24 +0000101 private ShadowPackageManager mShadowPackageManager;
Pavel Grafov6e0960b2018-01-19 12:27:35 +0000102
103 @Before
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
Pavel Grafov8b7af342018-02-01 18:48:24 +0000111 final PackageManager packageManager = RuntimeEnvironment.application.getPackageManager();
112 mShadowPackageManager = shadowOf(packageManager);
113
Pavel Grafov6e0960b2018-01-19 12:27:35 +0000114 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
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
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
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
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
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
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
Pavel Grafov8b7af342018-02-01 18:48:24 +0000202 @Test
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
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
Pavel Grafov6e0960b2018-01-19 12:27:35 +0000220 private void setUpLoggingAndAccess(boolean loggingEnabled) {
221 doReturn(loggingEnabled).when(mockInjector).isSecurityLoggingEnabled();
Pavel Grafov8b7af342018-02-01 18:48:24 +0000222
223 // Pretend that the caller is system.
224 setUpCaller(1000, "android.uid.system:1000");
225 }
226
227 private void setUpCaller(int uid, String packageName) {
228 doReturn(uid).when(mockInjector).getCallingUid();
229 mShadowPackageManager.setNameForUid(uid, packageName);
Pavel Grafov6e0960b2018-01-19 12:27:35 +0000230 }
231}