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 android.content.res.AssetManager;
20 import android.test.InstrumentationTestCase;
21 
22 import java.io.File;
23 import java.io.IOException;
24 import java.io.InputStream;
25 import java.security.KeyStore;
26 import java.security.KeyStoreException;
27 import java.security.MessageDigest;
28 import java.security.NoSuchAlgorithmException;
29 import java.security.cert.CertificateEncodingException;
30 import java.security.cert.CertificateException;
31 import java.security.cert.CertificateFactory;
32 import java.security.cert.X509Certificate;
33 import java.util.Arrays;
34 import java.util.Collections;
35 import java.util.HashSet;
36 import java.util.List;
37 import java.util.Set;
38 
39 public class CertificateTest extends InstrumentationTestCase {
40 
testNoRemovedCertificates()41     public void testNoRemovedCertificates() throws Exception {
42         Set<String> expectedCertificates = new HashSet<String>(
43                 Arrays.asList(CertificateData.CERTIFICATE_DATA));
44         Set<String> deviceCertificates = getDeviceCertificates();
45         expectedCertificates.removeAll(deviceCertificates);
46         assertEquals("Missing CA certificates", Collections.EMPTY_SET, expectedCertificates);
47     }
48 
49     /**
50      * If you fail CTS as a result of adding a root CA that is not part of the Android root CA
51      * store, please see the following.
52      *
53      * First, this test exists because adding untrustworthy root CAs to a device has a very
54      * significant security impact. In the worst case, adding a rogue CA can permanently compromise
55      * the confidentiality and integrity of your users' network traffic. Because of this risk,
56      * adding new certificates should be done sparingly and as a last resort -- never as a first
57      * response or short term fix. Before attempting to modify this test, please consider whether
58      * adding a new certificate authority is in your users' best interests.
59      *
60      * Second, because the addition of a new root CA by an OEM can have such dire consequences for
61      * so many people it is imperative that it be done transparently and in the open. Any request to
62      * modify the certificate list used by this test must have a corresponding change in AOSP
63      * (one certificate per change) authored by the OEM in question and including:
64      *
65      *     - the certificate in question:
66      *       - The certificate must be in a file under
67      *         cts/tests/tests/security/assets/oem_cacerts, in PEM (Privacy-enhanced Electronic
68      *         Mail) format, with the textual representation of the certificate following the PEM
69      *         section.
70      *       - The file name must be in the format of <hash>.<n> where "hash" is the subject hash
71      *         produced by:
72      *           openssl x509 -in cert_file -subject_hash -noout
73      *         and the "n" is a unique integer identifier starting at 0 to deal with collisions.
74      *         See OpenSSL's c_rehash manpage for details.
75      *       - cts/tests/tests/security/tools/format_cert.sh helps meet the above requirements.
76      *
77      *     - information about who created and maintains both the certificate and the corresponding
78      *       keypair.
79      *
80      *     - information about what the certificate is to be used for and why the certificate is
81      *       appropriate for inclusion.
82      *
83      *     - a statement from the OEM indicating that they have sufficient confidence in the
84      *       security of the key, the security practices of the issuer, and the validity of the
85      *       intended use that they believe adding the certificate is not detrimental to the
86      *       security of the user.
87      *
88      * Finally, please note that this is not the usual process for adding root CAs to Android. If
89      * you have a certificate that you believe should be present on all Android devices, please file
90      * a public bug at https://code.google.com/p/android/issues/entry or http://b.android.com to
91      * seek resolution.
92      *
93      * For questions, comments, and code reviews please contact security@android.com.
94      */
testNoAddedCertificates()95     public void testNoAddedCertificates() throws Exception {
96         Set<String> oemWhitelistedCertificates = getOemWhitelistedCertificates();
97         Set<String> expectedCertificates = new HashSet<String>(
98                 Arrays.asList(CertificateData.CERTIFICATE_DATA));
99         Set<String> deviceCertificates = getDeviceCertificates();
100         deviceCertificates.removeAll(expectedCertificates);
101         deviceCertificates.removeAll(oemWhitelistedCertificates);
102         assertEquals("Unknown CA certificates", Collections.EMPTY_SET, deviceCertificates);
103     }
104 
testBlockCertificates()105     public void testBlockCertificates() throws Exception {
106         Set<String> blockCertificates = new HashSet<String>();
107         blockCertificates.add("C0:60:ED:44:CB:D8:81:BD:0E:F8:6C:0B:A2:87:DD:CF:81:67:47:8C");
108 
109         Set<String> deviceCertificates = getDeviceCertificates();
110         deviceCertificates.retainAll(blockCertificates);
111         assertEquals("Blocked CA certificates", Collections.EMPTY_SET, deviceCertificates);
112     }
113 
getDeviceCertificates()114     private Set<String> getDeviceCertificates() throws KeyStoreException,
115             NoSuchAlgorithmException, CertificateException, IOException {
116         KeyStore keyStore = KeyStore.getInstance("AndroidCAStore");
117         keyStore.load(null, null);
118 
119         List<String> aliases = Collections.list(keyStore.aliases());
120         assertFalse(aliases.isEmpty());
121 
122         Set<String> certificates = new HashSet<String>();
123         for (String alias : aliases) {
124             assertTrue(keyStore.isCertificateEntry(alias));
125             X509Certificate certificate = (X509Certificate) keyStore.getCertificate(alias);
126             assertEquals(certificate.getSubjectUniqueID(), certificate.getIssuerUniqueID());
127             assertNotNull(certificate.getSubjectDN());
128             assertNotNull(certificate.getIssuerDN());
129             String fingerprint = getFingerprint(certificate);
130             certificates.add(fingerprint);
131         }
132         return certificates;
133     }
134 
135     private static final String ASSETS_DIR_OEM_CERTS = "oem_cacerts";
136 
getOemWhitelistedCertificates()137     private Set<String> getOemWhitelistedCertificates() throws Exception {
138         Set<String> certificates = new HashSet<String>();
139         CertificateFactory certFactory = CertificateFactory.getInstance("X.509");
140         AssetManager assetManager = getInstrumentation().getContext().getAssets();
141         for (String path : assetManager.list(ASSETS_DIR_OEM_CERTS)) {
142             File certAssetFile = new File(ASSETS_DIR_OEM_CERTS, path);
143             InputStream in = null;
144             try {
145                 in = assetManager.open(certAssetFile.toString());
146                 X509Certificate certificate = (X509Certificate) certFactory.generateCertificate(in);
147                 certificates.add(getFingerprint(certificate));
148             } catch (Exception e) {
149                 throw new Exception("Failed to load certificate from asset: " + certAssetFile, e);
150             } finally {
151                 if (in != null) {
152                     in.close();
153                 }
154             }
155         }
156         return certificates;
157     }
158 
getFingerprint(X509Certificate certificate)159     private String getFingerprint(X509Certificate certificate) throws CertificateEncodingException,
160             NoSuchAlgorithmException {
161         MessageDigest messageDigest = MessageDigest.getInstance("SHA1");
162         messageDigest.update(certificate.getEncoded());
163         byte[] sha1 = messageDigest.digest();
164         return convertToHexFingerprint(sha1);
165     }
166 
convertToHexFingerprint(byte[] sha1)167     private String convertToHexFingerprint(byte[] sha1) {
168         StringBuilder fingerprint = new StringBuilder();
169         for (int i = 0; i < sha1.length; i++) {
170             fingerprint.append(String.format("%02X", sha1[i]));
171             if (i + 1 < sha1.length) {
172                 fingerprint.append(":");
173             }
174         }
175         return fingerprint.toString();
176     }
177 }
178