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