- add sources.
[platform/framework/web/crosswalk.git] / src / net / android / java / src / org / chromium / net / X509Util.java
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.
4
5 package org.chromium.net;
6
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;
13
14 import org.chromium.base.JNINamespace;
15 import org.chromium.net.CertVerifyResultAndroid;
16
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;
29
30 import javax.net.ssl.TrustManager;
31 import javax.net.ssl.TrustManagerFactory;
32 import javax.net.ssl.X509TrustManager;
33
34 @JNINamespace("net")
35 public class X509Util {
36
37     private static final String TAG = "X509Util";
38
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)) {
42                 try {
43                     reloadDefaultTrustManager();
44                 }
45                 catch (CertificateException e) {
46                     Log.e(TAG, "Unable to reload the default TrustManager", e);
47                 }
48                 catch (KeyStoreException e) {
49                     Log.e(TAG, "Unable to reload the default TrustManager", e);
50                 }
51                 catch (NoSuchAlgorithmException e) {
52                     Log.e(TAG, "Unable to reload the default TrustManager", e);
53                 }
54             }
55         }
56     }
57
58     private static CertificateFactory sCertificateFactory;
59
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):
63     //    Netscape:
64     private static final String OID_SERVER_GATED_NETSCAPE = "2.16.840.1.113730.4.1";
65     //    Microsoft:
66     private static final String OID_SERVER_GATED_MICROSOFT = "1.3.6.1.4.1.311.10.3.3";
67
68     /**
69      * Trust manager backed up by the read-only system certificate store.
70      */
71     private static X509TrustManager sDefaultTrustManager;
72
73     /**
74      * BroadcastReceiver that listens to change in the system keystore to invalidate certificate
75      * caches.
76      */
77     private static TrustStorageListener sTrustStorageListener;
78
79     /**
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.
82      */
83     private static X509TrustManager sTestTrustManager;
84     private static KeyStore sTestKeyStore;
85
86     /**
87      * Lock object used to synchronize all calls that modify or depend on the trust managers.
88      */
89     private static final Object sLock = new Object();
90
91     /*
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.
95      */
96     private static boolean sDisableCertificateObservationForTest = false;
97
98     /**
99      * Ensures that the trust managers and certificate factory are initialized.
100      */
101     private static void ensureInitialized() throws CertificateException,
102             KeyStoreException, NoSuchAlgorithmException {
103         synchronized(sLock) {
104             if (sCertificateFactory == null) {
105                 sCertificateFactory = CertificateFactory.getInstance("X.509");
106             }
107             if (sDefaultTrustManager == null) {
108                 sDefaultTrustManager = X509Util.createTrustManager(null);
109             }
110             if (sTestKeyStore == null) {
111                 sTestKeyStore = KeyStore.getInstance(KeyStore.getDefaultType());
112                 try {
113                     sTestKeyStore.load(null);
114                 } catch(IOException e) {}  // No IO operation is attempted.
115             }
116             if (sTestTrustManager == null) {
117                 sTestTrustManager = X509Util.createTrustManager(sTestKeyStore);
118             }
119             if (!sDisableCertificateObservationForTest &&
120                     sTrustStorageListener == null) {
121                 sTrustStorageListener = new TrustStorageListener();
122                 nativeGetApplicationContext().registerReceiver(sTrustStorageListener,
123                         new IntentFilter(KeyChain.ACTION_STORAGE_CHANGED));
124             }
125         }
126     }
127
128     /**
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.
132      */
133     private static X509TrustManager createTrustManager(KeyStore keyStore) throws KeyStoreException,
134             NoSuchAlgorithmException {
135         String algorithm = TrustManagerFactory.getDefaultAlgorithm();
136         TrustManagerFactory tmf = TrustManagerFactory.getInstance(algorithm);
137         tmf.init(keyStore);
138
139         for (TrustManager tm : tmf.getTrustManagers()) {
140             if (tm instanceof X509TrustManager) {
141                 return (X509TrustManager) tm;
142             }
143         }
144         return null;
145     }
146
147     /**
148      * After each modification of test key store, trust manager has to be generated again.
149      */
150     private static void reloadTestTrustManager() throws KeyStoreException,
151             NoSuchAlgorithmException {
152         sTestTrustManager = X509Util.createTrustManager(sTestKeyStore);
153     }
154
155     /**
156      * After each modification by the system of the key store, trust manager has to be regenerated.
157      */
158     private static void reloadDefaultTrustManager() throws KeyStoreException,
159             NoSuchAlgorithmException, CertificateException {
160         sDefaultTrustManager = null;
161         nativeNotifyKeyChainChanged();
162         ensureInitialized();
163     }
164
165     /**
166      * Convert a DER encoded certificate to an X509Certificate.
167      */
168     public static X509Certificate createCertificateFromBytes(byte[] derBytes) throws
169             CertificateException, KeyStoreException, NoSuchAlgorithmException {
170         ensureInitialized();
171         return (X509Certificate) sCertificateFactory.generateCertificate(
172                 new ByteArrayInputStream(derBytes));
173     }
174
175     public static void addTestRootCertificate(byte[] rootCertBytes) throws CertificateException,
176             KeyStoreException, NoSuchAlgorithmException {
177         ensureInitialized();
178         X509Certificate rootCert = createCertificateFromBytes(rootCertBytes);
179         synchronized (sLock) {
180             sTestKeyStore.setCertificateEntry(
181                     "root_cert_" + Integer.toString(sTestKeyStore.size()), rootCert);
182             reloadTestTrustManager();
183         }
184     }
185
186     public static void clearTestRootCertificates() throws NoSuchAlgorithmException,
187             CertificateException, KeyStoreException {
188         ensureInitialized();
189         synchronized (sLock) {
190             try {
191                 sTestKeyStore.load(null);
192                 reloadTestTrustManager();
193             } catch (IOException e) {}  // No IO operation is attempted.
194         }
195     }
196
197     /**
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.
200      *
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.
203      *
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.
206      */
207     static boolean verifyKeyUsage(X509Certificate certificate) throws CertificateException {
208         List<String> ekuOids;
209         try {
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
215             return false;
216         }
217         if (ekuOids == null)
218             return true;
219
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)) {
225                 return true;
226             }
227         }
228
229         return false;
230     }
231
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);
237         }
238
239         try {
240             ensureInitialized();
241         } catch (CertificateException e) {
242             return CertVerifyResultAndroid.VERIFY_FAILED;
243         }
244
245         X509Certificate[] serverCertificates = new X509Certificate[certChain.length];
246         try {
247             for (int i = 0; i < certChain.length; ++i) {
248                 serverCertificates[i] = createCertificateFromBytes(certChain[i]);
249             }
250         } catch (CertificateException e) {
251             return CertVerifyResultAndroid.VERIFY_UNABLE_TO_PARSE;
252         }
253
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
257         // separately.
258         try {
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;
268         }
269
270         synchronized (sLock) {
271             try {
272                 sDefaultTrustManager.checkServerTrusted(serverCertificates, authType);
273                 return CertVerifyResultAndroid.VERIFY_OK;
274             } catch (CertificateException eDefaultManager) {
275                 try {
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;
284                 }
285             }
286         }
287     }
288
289     public static void setDisableCertificateObservationForTest(boolean disabled) {
290         sDisableCertificateObservationForTest = disabled;
291     }
292     /**
293      * Notify the native net::CertDatabase instance that the system database has been updated.
294      */
295     private static native void nativeNotifyKeyChainChanged();
296
297     /**
298      * Returns the application context.
299      */
300     private static native Context nativeGetApplicationContext();
301
302 }