1 /* 2 * Copyright (C) 2011 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 android.security.cts; 18 19 import static org.junit.Assert.assertEquals; 20 import static org.junit.Assert.assertFalse; 21 import static org.junit.Assert.assertNotNull; 22 import static org.junit.Assert.assertTrue; 23 24 import android.content.Context; 25 import android.content.pm.PackageManager; 26 27 import androidx.test.platform.app.InstrumentationRegistry; 28 29 import org.junit.Test; 30 import org.junit.runner.RunWith; 31 import org.junit.runners.Parameterized; 32 import org.junit.runners.Parameterized.Parameter; 33 import org.junit.runners.Parameterized.Parameters; 34 35 import java.io.File; 36 import java.io.FileInputStream; 37 import java.io.IOException; 38 import java.security.KeyStore; 39 import java.security.KeyStoreException; 40 import java.security.MessageDigest; 41 import java.security.NoSuchAlgorithmException; 42 import java.security.cert.Certificate; 43 import java.security.cert.CertificateEncodingException; 44 import java.security.cert.CertificateException; 45 import java.security.cert.CertificateFactory; 46 import java.security.cert.X509Certificate; 47 import java.util.Arrays; 48 import java.util.Collections; 49 import java.util.HashSet; 50 import java.util.List; 51 import java.util.Set; 52 53 @RunWith(Parameterized.class) 54 public class CertificateTest { 55 @Parameter 56 public String mApexCertsEnabled; 57 58 @Parameters(name = "{0}") data()59 public static Object[] data() { 60 return new Object[] {"true", "false"}; 61 } 62 63 // The directory for CA root certificates trusted by WFA (WiFi Alliance) 64 private static final String DIR_OF_CACERTS_FOR_WFA = 65 "/apex/com.android.wifi/etc/security/cacerts_wfa"; 66 67 @Test testNoRemovedCertificates()68 public void testNoRemovedCertificates() throws Exception { 69 System.setProperty("system.certs.enabled", mApexCertsEnabled); 70 Set<String> expectedCertificates = new HashSet<String>( 71 Arrays.asList(CertificateData.CERTIFICATE_DATA)); 72 Set<String> deviceCertificates = getDeviceCertificates(); 73 expectedCertificates.removeAll(deviceCertificates); 74 assertEquals("Missing CA certificates", Collections.EMPTY_SET, expectedCertificates); 75 } 76 77 /** 78 * If you fail CTS as a result of adding a root CA that is not part of the Android root CA 79 * store, please see the following. 80 * 81 * <p>This test exists because adding root CAs to a device has a very significant security 82 * impact. Whoever has access to the signing keys of that CA can compromise secure network 83 * traffic from affected Android devices, putting users at risk. 84 * 85 * <p>If you have a CA certificate which needs to be trusted by a particular app/service, 86 * ask the developer of the app/service to modify it to trust this CA (e.g., using Network 87 * Security Config feature). This avoids compromising the security of network traffic of other 88 * apps on the device. 89 * 90 * <p>If you have a CA certificate that you believe should be present on all Android devices, 91 * please file a public bug at https://code.google.com/p/android/issues/entry. 92 * 93 * <p>For questions, comments, and code reviews please contact security@android.com. 94 */ 95 @Test testNoAddedCertificates()96 public void testNoAddedCertificates() throws Exception { 97 System.setProperty("system.certs.enabled", mApexCertsEnabled); 98 Set<String> expectedCertificates = new HashSet<String>( 99 Arrays.asList(CertificateData.CERTIFICATE_DATA)); 100 Set<String> deviceCertificates = getDeviceCertificates(); 101 deviceCertificates.removeAll(expectedCertificates); 102 assertEquals("Unknown CA certificates", Collections.EMPTY_SET, deviceCertificates); 103 } 104 105 @Test testBlockCertificates()106 public void testBlockCertificates() throws Exception { 107 System.setProperty("system.certs.enabled", mApexCertsEnabled); 108 Set<String> blockCertificates = new HashSet<String>(); 109 blockCertificates.add("C0:60:ED:44:CB:D8:81:BD:0E:F8:6C:0B:A2:87:DD:CF:81:67:47:8C"); 110 111 Set<String> deviceCertificates = getDeviceCertificates(); 112 deviceCertificates.retainAll(blockCertificates); 113 assertEquals("Blocked CA certificates", Collections.EMPTY_SET, deviceCertificates); 114 } 115 116 /** 117 * This test exists because adding new ca certificate or removing the ca certificates trusted by 118 * WFA (WiFi Alliance) is not allowed. 119 * 120 * For questions, comments, and code reviews please contact security@android.com. 121 */ 122 @Test testNoRemovedWfaCertificates()123 public void testNoRemovedWfaCertificates() throws Exception { 124 if (!isWifiSupported()) { 125 return; 126 } 127 Set<String> expectedCertificates = new HashSet<>( 128 Arrays.asList(CertificateData.WFA_CERTIFICATE_DATA)); 129 Set<String> deviceWfaCertificates = getDeviceWfaCertificates(); 130 expectedCertificates.removeAll(deviceWfaCertificates); 131 assertEquals("Missing WFA CA certificates", Collections.EMPTY_SET, expectedCertificates); 132 } 133 134 @Test testNoAddedWfaCertificates()135 public void testNoAddedWfaCertificates() throws Exception { 136 if (!isWifiSupported()) { 137 return; 138 } 139 Set<String> expectedCertificates = new HashSet<String>( 140 Arrays.asList(CertificateData.WFA_CERTIFICATE_DATA)); 141 Set<String> deviceWfaCertificates = getDeviceWfaCertificates(); 142 deviceWfaCertificates.removeAll(expectedCertificates); 143 assertEquals("Unknown WFA CA certificates", Collections.EMPTY_SET, deviceWfaCertificates); 144 } 145 isWifiSupported()146 private boolean isWifiSupported() { 147 Context context; 148 context = InstrumentationRegistry.getInstrumentation().getContext(); 149 return context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WIFI); 150 } 151 createWfaKeyStore()152 private KeyStore createWfaKeyStore() throws CertificateException, IOException, 153 KeyStoreException, NoSuchAlgorithmException { 154 KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType()); 155 keyStore.load(null, null); 156 int index = 0; 157 for (X509Certificate cert : loadCertsFromDisk()) { 158 keyStore.setCertificateEntry(String.format("%d", index++), cert); 159 } 160 return keyStore; 161 } 162 loadCertsFromDisk()163 private Set<X509Certificate> loadCertsFromDisk() throws CertificateException, 164 IOException { 165 Set<X509Certificate> certs = new HashSet<>(); 166 File certDir = new File(DIR_OF_CACERTS_FOR_WFA); 167 File[] certFiles = certDir.listFiles(); 168 if (certFiles == null || certFiles.length <= 0) { 169 return certs; 170 } 171 CertificateFactory certFactory = CertificateFactory.getInstance("X.509"); 172 for (File certFile : certFiles) { 173 FileInputStream fis = new FileInputStream(certFile); 174 Certificate cert = certFactory.generateCertificate(fis); 175 if (cert instanceof X509Certificate) { 176 certs.add((X509Certificate) cert); 177 } 178 fis.close(); 179 } 180 return certs; 181 } 182 getDeviceWfaCertificates()183 private Set<String> getDeviceWfaCertificates() throws KeyStoreException, 184 NoSuchAlgorithmException, CertificateException, IOException { 185 KeyStore wfaKeyStore = createWfaKeyStore(); 186 List<String> aliases = Collections.list(wfaKeyStore.aliases()); 187 assertFalse(aliases.isEmpty()); 188 189 Set<String> certificates = new HashSet<>(); 190 for (String alias : aliases) { 191 assertTrue(wfaKeyStore.isCertificateEntry(alias)); 192 X509Certificate certificate = (X509Certificate) wfaKeyStore.getCertificate(alias); 193 assertEquals(certificate.getSubjectUniqueID(), certificate.getIssuerUniqueID()); 194 assertNotNull(certificate.getSubjectDN()); 195 assertNotNull(certificate.getIssuerDN()); 196 String fingerprint = getFingerprint(certificate); 197 certificates.add(fingerprint); 198 } 199 return certificates; 200 } 201 getDeviceCertificates()202 private Set<String> getDeviceCertificates() throws KeyStoreException, 203 NoSuchAlgorithmException, CertificateException, IOException { 204 KeyStore keyStore = KeyStore.getInstance("AndroidCAStore"); 205 keyStore.load(null, null); 206 207 List<String> aliases = Collections.list(keyStore.aliases()); 208 assertFalse(aliases.isEmpty()); 209 210 Set<String> certificates = new HashSet<String>(); 211 for (String alias : aliases) { 212 assertTrue(keyStore.isCertificateEntry(alias)); 213 X509Certificate certificate = (X509Certificate) keyStore.getCertificate(alias); 214 assertEquals(certificate.getSubjectUniqueID(), certificate.getIssuerUniqueID()); 215 assertNotNull(certificate.getSubjectDN()); 216 assertNotNull(certificate.getIssuerDN()); 217 String fingerprint = getFingerprint(certificate); 218 certificates.add(fingerprint); 219 } 220 return certificates; 221 } 222 getFingerprint(X509Certificate certificate)223 private String getFingerprint(X509Certificate certificate) throws CertificateEncodingException, 224 NoSuchAlgorithmException { 225 MessageDigest messageDigest = MessageDigest.getInstance("SHA1"); 226 messageDigest.update(certificate.getEncoded()); 227 byte[] sha1 = messageDigest.digest(); 228 return convertToHexFingerprint(sha1); 229 } 230 convertToHexFingerprint(byte[] sha1)231 private String convertToHexFingerprint(byte[] sha1) { 232 StringBuilder fingerprint = new StringBuilder(); 233 for (int i = 0; i < sha1.length; i++) { 234 fingerprint.append(String.format("%02X", sha1[i])); 235 if (i + 1 < sha1.length) { 236 fingerprint.append(":"); 237 } 238 } 239 return fingerprint.toString(); 240 } 241 } 242