X509Util.java revision 1e9bf3e0803691d0a228da41fc608347b6db4340
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 5package org.chromium.net; 6 7import android.content.BroadcastReceiver; 8import android.content.Context; 9import android.content.Intent; 10import android.content.IntentFilter; 11import android.security.KeyChain; 12import android.util.Log; 13 14import org.chromium.base.JNINamespace; 15import org.chromium.net.CertVerifyResultAndroid; 16 17import java.io.ByteArrayInputStream; 18import java.io.IOException; 19import java.security.KeyStore; 20import java.security.KeyStoreException; 21import java.security.NoSuchAlgorithmException; 22import java.security.cert.CertificateException; 23import java.security.cert.CertificateExpiredException; 24import java.security.cert.CertificateNotYetValidException; 25import java.security.cert.CertificateFactory; 26import java.security.cert.CertificateParsingException; 27import java.security.cert.X509Certificate; 28import java.util.List; 29 30import javax.net.ssl.TrustManager; 31import javax.net.ssl.TrustManagerFactory; 32import javax.net.ssl.X509TrustManager; 33 34@JNINamespace("net") 35public 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} 303