1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 package org.chromium.net;
7 import android.content.BroadcastReceiver;
8 import android.content.Context;
9 import android.content.Intent;
10 import android.content.IntentFilter;
11 import android.security.KeyChain;
12 import android.util.Log;
14 import org.chromium.base.JNINamespace;
15 import org.chromium.net.CertVerifyResultAndroid;
17 import java.io.ByteArrayInputStream;
18 import java.io.IOException;
19 import java.security.KeyStore;
20 import java.security.KeyStoreException;
21 import java.security.NoSuchAlgorithmException;
22 import java.security.cert.CertificateException;
23 import java.security.cert.CertificateExpiredException;
24 import java.security.cert.CertificateNotYetValidException;
25 import java.security.cert.CertificateFactory;
26 import java.security.cert.CertificateParsingException;
27 import java.security.cert.X509Certificate;
28 import java.util.List;
30 import javax.net.ssl.TrustManager;
31 import javax.net.ssl.TrustManagerFactory;
32 import javax.net.ssl.X509TrustManager;
35 public class X509Util {
37 private static final String TAG = "X509Util";
39 public static final class TrustStorageListener extends BroadcastReceiver {
40 @Override public void onReceive(Context context, Intent intent) {
41 if (intent.getAction().equals(KeyChain.ACTION_STORAGE_CHANGED)) {
43 reloadDefaultTrustManager();
45 catch (CertificateException e) {
46 Log.e(TAG, "Unable to reload the default TrustManager", e);
48 catch (KeyStoreException e) {
49 Log.e(TAG, "Unable to reload the default TrustManager", e);
51 catch (NoSuchAlgorithmException e) {
52 Log.e(TAG, "Unable to reload the default TrustManager", e);
58 private static CertificateFactory sCertificateFactory;
60 private static final String OID_TLS_SERVER_AUTH = "1.3.6.1.5.5.7.3.1";
61 private static final String OID_ANY_EKU = "2.5.29.37.0";
62 // Server-Gated Cryptography (necessary to support a few legacy issuers):
64 private static final String OID_SERVER_GATED_NETSCAPE = "2.16.840.1.113730.4.1";
66 private static final String OID_SERVER_GATED_MICROSOFT = "1.3.6.1.4.1.311.10.3.3";
69 * Trust manager backed up by the read-only system certificate store.
71 private static X509TrustManager sDefaultTrustManager;
74 * BroadcastReceiver that listens to change in the system keystore to invalidate certificate
77 private static TrustStorageListener sTrustStorageListener;
80 * Trust manager backed up by a custom certificate store. We need such manager to plant test
81 * root CA to the trust store in testing.
83 private static X509TrustManager sTestTrustManager;
84 private static KeyStore sTestKeyStore;
87 * Lock object used to synchronize all calls that modify or depend on the trust managers.
89 private static final Object sLock = new Object();
92 * Allow disabling registering the observer for the certificat changes. Net unit tests do not
93 * load native libraries which prevent this to succeed. Moreover, the system does not allow to
94 * interact with the certificate store without user interaction.
96 private static boolean sDisableCertificateObservationForTest = false;
99 * Ensures that the trust managers and certificate factory are initialized.
101 private static void ensureInitialized() throws CertificateException,
102 KeyStoreException, NoSuchAlgorithmException {
103 synchronized(sLock) {
104 if (sCertificateFactory == null) {
105 sCertificateFactory = CertificateFactory.getInstance("X.509");
107 if (sDefaultTrustManager == null) {
108 sDefaultTrustManager = X509Util.createTrustManager(null);
110 if (sTestKeyStore == null) {
111 sTestKeyStore = KeyStore.getInstance(KeyStore.getDefaultType());
113 sTestKeyStore.load(null);
114 } catch(IOException e) {} // No IO operation is attempted.
116 if (sTestTrustManager == null) {
117 sTestTrustManager = X509Util.createTrustManager(sTestKeyStore);
119 if (!sDisableCertificateObservationForTest &&
120 sTrustStorageListener == null) {
121 sTrustStorageListener = new TrustStorageListener();
122 nativeGetApplicationContext().registerReceiver(sTrustStorageListener,
123 new IntentFilter(KeyChain.ACTION_STORAGE_CHANGED));
129 * Creates a X509TrustManager backed up by the given key store. When null is passed as a key
130 * store, system default trust store is used.
131 * @throws KeyStoreException, NoSuchAlgorithmException on error initializing the TrustManager.
133 private static X509TrustManager createTrustManager(KeyStore keyStore) throws KeyStoreException,
134 NoSuchAlgorithmException {
135 String algorithm = TrustManagerFactory.getDefaultAlgorithm();
136 TrustManagerFactory tmf = TrustManagerFactory.getInstance(algorithm);
139 for (TrustManager tm : tmf.getTrustManagers()) {
140 if (tm instanceof X509TrustManager) {
141 return (X509TrustManager) tm;
148 * After each modification of test key store, trust manager has to be generated again.
150 private static void reloadTestTrustManager() throws KeyStoreException,
151 NoSuchAlgorithmException {
152 sTestTrustManager = X509Util.createTrustManager(sTestKeyStore);
156 * After each modification by the system of the key store, trust manager has to be regenerated.
158 private static void reloadDefaultTrustManager() throws KeyStoreException,
159 NoSuchAlgorithmException, CertificateException {
160 sDefaultTrustManager = null;
161 nativeNotifyKeyChainChanged();
166 * Convert a DER encoded certificate to an X509Certificate.
168 public static X509Certificate createCertificateFromBytes(byte[] derBytes) throws
169 CertificateException, KeyStoreException, NoSuchAlgorithmException {
171 return (X509Certificate) sCertificateFactory.generateCertificate(
172 new ByteArrayInputStream(derBytes));
175 public static void addTestRootCertificate(byte[] rootCertBytes) throws CertificateException,
176 KeyStoreException, NoSuchAlgorithmException {
178 X509Certificate rootCert = createCertificateFromBytes(rootCertBytes);
179 synchronized (sLock) {
180 sTestKeyStore.setCertificateEntry(
181 "root_cert_" + Integer.toString(sTestKeyStore.size()), rootCert);
182 reloadTestTrustManager();
186 public static void clearTestRootCertificates() throws NoSuchAlgorithmException,
187 CertificateException, KeyStoreException {
189 synchronized (sLock) {
191 sTestKeyStore.load(null);
192 reloadTestTrustManager();
193 } catch (IOException e) {} // No IO operation is attempted.
198 * If an EKU extension is present in the end-entity certificate, it MUST contain either the
199 * anyEKU or serverAuth or netscapeSGC or Microsoft SGC EKUs.
201 * @return true if there is no EKU extension or if any of the EKU extensions is one of the valid
202 * OIDs for web server certificates.
204 * TODO(palmer): This can be removed after the equivalent change is made to the Android default
205 * TrustManager and that change is shipped to a large majority of Android users.
207 static boolean verifyKeyUsage(X509Certificate certificate) throws CertificateException {
208 List<String> ekuOids;
210 ekuOids = certificate.getExtendedKeyUsage();
211 } catch (NullPointerException e) {
212 // getExtendedKeyUsage() can crash due to an Android platform bug. This probably
213 // happens when the EKU extension data is malformed so return false here.
214 // See http://crbug.com/233610
220 for (String ekuOid : ekuOids) {
221 if (ekuOid.equals(OID_TLS_SERVER_AUTH) ||
222 ekuOid.equals(OID_ANY_EKU) ||
223 ekuOid.equals(OID_SERVER_GATED_NETSCAPE) ||
224 ekuOid.equals(OID_SERVER_GATED_MICROSOFT)) {
232 public static int verifyServerCertificates(byte[][] certChain, String authType)
233 throws KeyStoreException, NoSuchAlgorithmException {
234 if (certChain == null || certChain.length == 0 || certChain[0] == null) {
235 throw new IllegalArgumentException("Expected non-null and non-empty certificate " +
236 "chain passed as |certChain|. |certChain|=" + certChain);
241 } catch (CertificateException e) {
242 return CertVerifyResultAndroid.VERIFY_FAILED;
245 X509Certificate[] serverCertificates = new X509Certificate[certChain.length];
247 for (int i = 0; i < certChain.length; ++i) {
248 serverCertificates[i] = createCertificateFromBytes(certChain[i]);
250 } catch (CertificateException e) {
251 return CertVerifyResultAndroid.VERIFY_UNABLE_TO_PARSE;
254 // Expired and not yet valid certificates would be rejected by the trust managers, but the
255 // trust managers report all certificate errors using the general CertificateException. In
256 // order to get more granular error information, cert validity time range is being checked
259 serverCertificates[0].checkValidity();
260 if (!verifyKeyUsage(serverCertificates[0]))
261 return CertVerifyResultAndroid.VERIFY_INCORRECT_KEY_USAGE;
262 } catch (CertificateExpiredException e) {
263 return CertVerifyResultAndroid.VERIFY_EXPIRED;
264 } catch (CertificateNotYetValidException e) {
265 return CertVerifyResultAndroid.VERIFY_NOT_YET_VALID;
266 } catch (CertificateException e) {
267 return CertVerifyResultAndroid.VERIFY_FAILED;
270 synchronized (sLock) {
272 sDefaultTrustManager.checkServerTrusted(serverCertificates, authType);
273 return CertVerifyResultAndroid.VERIFY_OK;
274 } catch (CertificateException eDefaultManager) {
276 sTestTrustManager.checkServerTrusted(serverCertificates, authType);
277 return CertVerifyResultAndroid.VERIFY_OK;
278 } catch (CertificateException eTestManager) {
279 // Neither of the trust managers confirms the validity of the certificate chain,
280 // log the error message returned by the system trust manager.
281 Log.i(TAG, "Failed to validate the certificate chain, error: " +
282 eDefaultManager.getMessage());
283 return CertVerifyResultAndroid.VERIFY_NO_TRUSTED_ROOT;
289 public static void setDisableCertificateObservationForTest(boolean disabled) {
290 sDisableCertificateObservationForTest = disabled;
293 * Notify the native net::CertDatabase instance that the system database has been updated.
295 private static native void nativeNotifyKeyChainChanged();
298 * Returns the application context.
300 private static native Context nativeGetApplicationContext();