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