Require actual certificates for testNoAddedCertificates.

Until now, this test required only SHA-1 fingerprints of OEM-added
certificates to be present. To ensure more transparency and future
extensibility this CL makes this test require full contents of
certificates.

P.S. The base class of this test was changed from AndroidTestCase to
InstrumentationTestCase because it now needs to access assets from
the test package. Assets contain permitted OEM-added certificates.

Bug: 11483665
Change-Id: I1a4ab0e4ad516102ec5f1fce855be5c2ba988666
diff --git a/tests/tests/security/src/android/security/cts/CertificateTest.java b/tests/tests/security/src/android/security/cts/CertificateTest.java
index e6e2a2b..3db7aca 100644
--- a/tests/tests/security/src/android/security/cts/CertificateTest.java
+++ b/tests/tests/security/src/android/security/cts/CertificateTest.java
@@ -16,9 +16,10 @@
 
 package android.security.cts;
 
-import android.test.AndroidTestCase;
+import android.content.res.AssetManager;
+import android.test.InstrumentationTestCase;
 
-import java.io.FileInputStream;
+import java.io.File;
 import java.io.IOException;
 import java.io.InputStream;
 import java.security.KeyStore;
@@ -27,6 +28,7 @@
 import java.security.NoSuchAlgorithmException;
 import java.security.cert.CertificateEncodingException;
 import java.security.cert.CertificateException;
+import java.security.cert.CertificateFactory;
 import java.security.cert.X509Certificate;
 import java.util.Arrays;
 import java.util.Collections;
@@ -34,7 +36,7 @@
 import java.util.List;
 import java.util.Set;
 
-public class CertificateTest extends AndroidTestCase {
+public class CertificateTest extends InstrumentationTestCase {
 
     public void testNoRemovedCertificates() throws Exception {
         Set<String> expectedCertificates = new HashSet<String>(
@@ -45,16 +47,58 @@
     }
 
     /**
-     * {@see OEMCertificateWhitelist#OEM_CERTIFICATE_WHITELIST} for more information on this test.
+     * If you fail CTS as a result of adding a root CA that is not part of the Android root CA
+     * store, please see the following.
+     *
+     * First, this test exists because adding untrustworthy root CAs to a device has a very
+     * significant security impact. In the worst case, adding a rogue CA can permanently compromise
+     * the confidentiality and integrity of your users' network traffic. Because of this risk,
+     * adding new certificates should be done sparingly and as a last resort -- never as a first
+     * response or short term fix. Before attempting to modify this test, please consider whether
+     * adding a new certificate authority is in your users' best interests.
+     *
+     * Second, because the addition of a new root CA by an OEM can have such dire consequences for
+     * so many people it is imperative that it be done transparently and in the open. Any request to
+     * modify the certificate list used by this test must have a corresponding change in AOSP
+     * (one certificate per change) authored by the OEM in question and including:
+     *
+     *     - the certificate in question:
+     *       - The certificate must be in a file under
+     *         cts/tests/tests/security/assets/oem_cacerts, in PEM (Privacy-enhanced Electronic
+     *         Mail) format, with the textual representation of the certificate following the PEM
+     *         section.
+     *       - The file name must be in the format of <hash>.<n> where "hash" is the subject hash
+     *         produced by:
+     *           openssl x509 -in cert_file -subject_hash -noout
+     *         and the "n" is a unique integer identifier starting at 0 to deal with collisions.
+     *         See OpenSSL's c_rehash manpage for details.
+     *       - cts/tests/tests/security/tools/format_cert.sh helps meet the above requirements.
+     *
+     *     - information about who created and maintains both the certificate and the corresponding
+     *       keypair.
+     *
+     *     - information about what the certificate is to be used for and why the certificate is
+     *       appropriate for inclusion.
+     *
+     *     - a statement from the OEM indicating that they have sufficient confidence in the
+     *       security of the key, the security practices of the issuer, and the validity of the
+     *       intended use that they believe adding the certificate is not detrimental to the
+     *       security of the user.
+     *
+     * Finally, please note that this is not the usual process for adding root CAs to Android. If
+     * you have a certificate that you believe should be present on all Android devices, please file
+     * a public bug at https://code.google.com/p/android/issues/entry or http://b.android.com to
+     * seek resolution.
+     *
+     * For questions, comments, and code reviews please contact security@android.com.
      */
     public void testNoAddedCertificates() throws Exception {
-        Set<String> oemCertificateWhitelist = new HashSet<String>(
-                Arrays.asList(OEMCertificateWhitelist.OEM_CERTIFICATE_WHITELIST));
+        Set<String> oemWhitelistedCertificates = getOemWhitelistedCertificates();
         Set<String> expectedCertificates = new HashSet<String>(
                 Arrays.asList(CertificateData.CERTIFICATE_DATA));
         Set<String> deviceCertificates = getDeviceCertificates();
         deviceCertificates.removeAll(expectedCertificates);
-        deviceCertificates.removeAll(oemCertificateWhitelist);
+        deviceCertificates.removeAll(oemWhitelistedCertificates);
         assertEquals("Unknown CA certificates", Collections.EMPTY_SET, deviceCertificates);
     }
 
@@ -88,6 +132,30 @@
         return certificates;
     }
 
+    private static final String ASSETS_DIR_OEM_CERTS = "oem_cacerts";
+
+    private Set<String> getOemWhitelistedCertificates() throws Exception {
+        Set<String> certificates = new HashSet<String>();
+        CertificateFactory certFactory = CertificateFactory.getInstance("X.509");
+        AssetManager assetManager = getInstrumentation().getContext().getAssets();
+        for (String path : assetManager.list(ASSETS_DIR_OEM_CERTS)) {
+            File certAssetFile = new File(ASSETS_DIR_OEM_CERTS, path);
+            InputStream in = null;
+            try {
+                in = assetManager.open(certAssetFile.toString());
+                X509Certificate certificate = (X509Certificate) certFactory.generateCertificate(in);
+                certificates.add(getFingerprint(certificate));
+            } catch (Exception e) {
+                throw new Exception("Failed to load certificate from asset: " + certAssetFile, e);
+            } finally {
+                if (in != null) {
+                    in.close();
+                }
+            }
+        }
+        return certificates;
+    }
+
     private String getFingerprint(X509Certificate certificate) throws CertificateEncodingException,
             NoSuchAlgorithmException {
         MessageDigest messageDigest = MessageDigest.getInstance("SHA1");
diff --git a/tests/tests/security/src/android/security/cts/OEMCertificateWhitelist.java b/tests/tests/security/src/android/security/cts/OEMCertificateWhitelist.java
deleted file mode 100644
index 024c15f..0000000
--- a/tests/tests/security/src/android/security/cts/OEMCertificateWhitelist.java
+++ /dev/null
@@ -1,67 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.security.cts;
-
-class OEMCertificateWhitelist {
-
-  /**
-   * If you fail CTS as a result of adding a root CA that is not part
-   * of the Android root CA store, please see the following.
-   *
-   * First, this test exists because adding untrustworthy root CAs
-   * to a device has a very significant security impact. In the worst
-   * case, adding a rogue CA to this list can permanently compromise
-   * the confidentiality and integrity of your users' network traffic.
-   * Because of this risk, adding new certificates should be done
-   * sparingly and as a last resort- never as a first response or
-   * short term fix. Before attempting to modify this test, please
-   * consider whether adding a new certificate authority is in your
-   * users' best interests.
-   *
-   * Second, because the addition of a new root CA by an OEM can have
-   * such dire consequences for so many people it is imperative that
-   * it be done transparently and in the open. Any request to modify
-   * this list must have a corresponding change in AOSP authored by
-   * the OEM in question and including:
-   *
-   *     - the certificate in question.
-   *
-   *     - information about who created and maintains
-   *       both the certificate and the corresponding keypair.
-   *
-   *     - information about what the certificate is to be used
-   *       for and why the certificate is appropriate for inclusion.
-   *
-   *     - a statement from the OEM indicating that they have
-   *       sufficient confidence in the security of the key, the
-   *       security practices of the issuer, and the validity
-   *       of the intended use that they believe adding the
-   *       certificate is not detrimental to the security of the
-   *       user.
-   *
-   * Finally, please note that this is not the usual process for
-   * adding root CAs to Android. If you have a certificate that you
-   * believe should be present on all Android devices, please file a
-   * public bug at https://code.google.com/p/android/issues/entry or
-   * http://b.android.com to seek resolution.
-   *
-   * For questions, comments, and code reviews please contact
-   * security@android.com.
-   */
-  static final String[] OEM_CERTIFICATE_WHITELIST = {};
-
-}
diff --git a/tests/tests/security/tools/format_cert.sh b/tests/tests/security/tools/format_cert.sh
new file mode 100755
index 0000000..94407a0
--- /dev/null
+++ b/tests/tests/security/tools/format_cert.sh
@@ -0,0 +1,55 @@
+#!/bin/sh
+
+# Outputs the provided certificate (PEM or DER) in a format used by CTS tests.
+# The format is PEM block, followed by the textual representation of the
+# certificate, followed by the SHA-1 fingerprint.
+
+# OpenSSL binary built from this Android source
+OPENSSL="$ANDROID_HOST_OUT/bin/openssl"
+if [ "$ANDROID_HOST_OUT" == "" ]; then
+  echo "Android build environment not set up"
+  echo
+  echo "Run the following from the root of the Android source tree:"
+  echo "  . build/envsetup.sh && lunch"
+  exit 1
+fi
+if [ ! -f "$OPENSSL" ]; then
+  echo "openssl binary not found"
+  echo
+  echo "Run 'mmm external/openssl' or 'make openssl' from the root of the" \
+      "Android source tree to build it."
+  exit 1
+fi
+
+# Input file containing the certificate in PEM or DER format
+in_file="$1"
+
+# Output file. If not specified, the file will be named <hash>.0 where "hash"
+# is the certificate's subject hash produced by:
+#   openssl x509 -in cert_file -subject_hash -noout
+out_file="$2"
+
+# Detect whether the input file is PEM or DER.
+in_form="pem"
+subject_hash=$("$OPENSSL" x509 -in "$in_file" -inform $in_form -subject_hash \
+    -noout 2>/dev/null)
+if [ "$?" != "0" ]; then
+  in_form="der"
+  subject_hash=$("$OPENSSL" x509 -in "$in_file" -inform $in_form -subject_hash \
+      -noout)
+  if [ "$?" != "0" ]; then
+    echo "Certificate file format is neither PEM nor DER"
+    exit 1
+  fi
+fi
+
+# Name the output file <hash>.0 if the name is not specified explicitly.
+if [ "$out_file" == "" ]; then
+  out_file="$subject_hash.0"
+  echo "Auto-generated output file name: $out_file"
+fi
+
+# Output the certificate in the target format
+"$OPENSSL" x509 -in "$in_file" -inform $in_form -outform pem > "$out_file" && \
+"$OPENSSL" x509 -in "$in_file" -inform $in_form -noout -text -fingerprint \
+    >> "$out_file"