1 /* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
2  * This Source Code Form is subject to the terms of the Mozilla Public
3  * License, v. 2.0. If a copy of the MPL was not distributed with this
4  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5 
6 package org.mozilla.gecko;
7 
8 import android.util.Log;
9 import java.io.IOException;
10 import java.security.KeyStore;
11 import java.security.KeyStoreException;
12 import java.security.NoSuchAlgorithmException;
13 import java.security.cert.Certificate;
14 import java.security.cert.CertificateEncodingException;
15 import java.security.cert.CertificateException;
16 import java.util.ArrayList;
17 import java.util.Enumeration;
18 import org.mozilla.gecko.annotation.WrapForJNI;
19 
20 // This class implements the functionality needed to find third-party root
21 // certificates that have been added to the android CA store.
22 public class EnterpriseRoots {
23   private static final String LOGTAG = "EnterpriseRoots";
24 
25   // Gecko calls this function from C++ to find third-party root certificates
26   // it can use as trust anchors for TLS connections.
27   @WrapForJNI
gatherEnterpriseRoots()28   private static byte[][] gatherEnterpriseRoots() {
29 
30     // The KeyStore "AndroidCAStore" contains the certificates we're
31     // interested in.
32     final KeyStore ks;
33     try {
34       ks = KeyStore.getInstance("AndroidCAStore");
35     } catch (final KeyStoreException kse) {
36       Log.e(LOGTAG, "getInstance() failed", kse);
37       return new byte[0][0];
38     }
39     try {
40       ks.load(null);
41     } catch (final CertificateException ce) {
42       Log.e(LOGTAG, "load() failed", ce);
43       return new byte[0][0];
44     } catch (final IOException ioe) {
45       Log.e(LOGTAG, "load() failed", ioe);
46       return new byte[0][0];
47     } catch (final NoSuchAlgorithmException nsae) {
48       Log.e(LOGTAG, "load() failed", nsae);
49       return new byte[0][0];
50     }
51     // Given the KeyStore, we get an identifier for each object in it. For
52     // each one that is a Certificate, we try to distinguish between
53     // entries that shipped with the OS and entries that were added by the
54     // user or an administrator. The former we ignore and the latter we
55     // collect in an array of byte arrays and return.
56     final Enumeration<String> aliases;
57     try {
58       aliases = ks.aliases();
59     } catch (final KeyStoreException kse) {
60       Log.e(LOGTAG, "aliases() failed", kse);
61       return new byte[0][0];
62     }
63     final ArrayList<byte[]> roots = new ArrayList<byte[]>();
64     while (aliases.hasMoreElements()) {
65       final String alias = aliases.nextElement();
66       final boolean isCertificate;
67       try {
68         isCertificate = ks.isCertificateEntry(alias);
69       } catch (final KeyStoreException kse) {
70         Log.e(LOGTAG, "isCertificateEntry() failed", kse);
71         continue;
72       }
73       // Built-in certificate aliases start with "system:", whereas
74       // 3rd-party certificate aliases start with "user:". It's
75       // unfortunate to be relying on this implementation detail, but
76       // there appears to be no other way to differentiate between the
77       // two.
78       if (isCertificate && alias.startsWith("user:")) {
79         final Certificate certificate;
80         try {
81           certificate = ks.getCertificate(alias);
82         } catch (final KeyStoreException kse) {
83           Log.e(LOGTAG, "getCertificate() failed", kse);
84           continue;
85         }
86         try {
87           roots.add(certificate.getEncoded());
88         } catch (final CertificateEncodingException cee) {
89           Log.e(LOGTAG, "getEncoded() failed", cee);
90         }
91       }
92     }
93     Log.d(LOGTAG, "found " + roots.size() + " enterprise roots");
94     return roots.toArray(new byte[0][0]);
95   }
96 }
97