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