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