1// Copyright 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; 15 16import java.io.ByteArrayInputStream; 17import java.io.IOException; 18import java.security.KeyStore; 19import java.security.KeyStoreException; 20import java.security.NoSuchAlgorithmException; 21import java.security.cert.CertificateException; 22import java.security.cert.CertificateExpiredException; 23import java.security.cert.CertificateFactory; 24import java.security.cert.CertificateNotYetValidException; 25import java.security.cert.X509Certificate; 26import java.util.List; 27 28import javax.net.ssl.TrustManager; 29import javax.net.ssl.TrustManagerFactory; 30import javax.net.ssl.X509TrustManager; 31 32@JNINamespace("net") 33public class X509Util { 34 35 private static final String TAG = "X509Util"; 36 37 public static final class TrustStorageListener extends BroadcastReceiver { 38 @Override public void onReceive(Context context, Intent intent) { 39 if (intent.getAction().equals(KeyChain.ACTION_STORAGE_CHANGED)) { 40 try { 41 reloadDefaultTrustManager(); 42 } 43 catch (CertificateException e) { 44 Log.e(TAG, "Unable to reload the default TrustManager", e); 45 } 46 catch (KeyStoreException e) { 47 Log.e(TAG, "Unable to reload the default TrustManager", e); 48 } 49 catch (NoSuchAlgorithmException e) { 50 Log.e(TAG, "Unable to reload the default TrustManager", e); 51 } 52 } 53 } 54 } 55 56 private static CertificateFactory sCertificateFactory; 57 58 private static final String OID_TLS_SERVER_AUTH = "1.3.6.1.5.5.7.3.1"; 59 private static final String OID_ANY_EKU = "2.5.29.37.0"; 60 // Server-Gated Cryptography (necessary to support a few legacy issuers): 61 // Netscape: 62 private static final String OID_SERVER_GATED_NETSCAPE = "2.16.840.1.113730.4.1"; 63 // Microsoft: 64 private static final String OID_SERVER_GATED_MICROSOFT = "1.3.6.1.4.1.311.10.3.3"; 65 66 /** 67 * Trust manager backed up by the read-only system certificate store. 68 */ 69 private static X509TrustManager sDefaultTrustManager; 70 71 /** 72 * BroadcastReceiver that listens to change in the system keystore to invalidate certificate 73 * caches. 74 */ 75 private static TrustStorageListener sTrustStorageListener; 76 77 /** 78 * Trust manager backed up by a custom certificate store. We need such manager to plant test 79 * root CA to the trust store in testing. 80 */ 81 private static X509TrustManager sTestTrustManager; 82 private static KeyStore sTestKeyStore; 83 84 /** 85 * Lock object used to synchronize all calls that modify or depend on the trust managers. 86 */ 87 private static final Object sLock = new Object(); 88 89 /* 90 * Allow disabling registering the observer for the certificat changes. Net unit tests do not 91 * load native libraries which prevent this to succeed. Moreover, the system does not allow to 92 * interact with the certificate store without user interaction. 93 */ 94 private static boolean sDisableCertificateObservationForTest = false; 95 96 /** 97 * Ensures that the trust managers and certificate factory are initialized. 98 */ 99 private static void ensureInitialized() throws CertificateException, 100 KeyStoreException, NoSuchAlgorithmException { 101 synchronized (sLock) { 102 if (sCertificateFactory == null) { 103 sCertificateFactory = CertificateFactory.getInstance("X.509"); 104 } 105 if (sDefaultTrustManager == null) { 106 sDefaultTrustManager = X509Util.createTrustManager(null); 107 } 108 if (sTestKeyStore == null) { 109 sTestKeyStore = KeyStore.getInstance(KeyStore.getDefaultType()); 110 try { 111 sTestKeyStore.load(null); 112 } catch (IOException e) { 113 // No IO operation is attempted. 114 } 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) { 194 // No IO operation is attempted. 195 } 196 } 197 } 198 199 /** 200 * If an EKU extension is present in the end-entity certificate, it MUST contain either the 201 * anyEKU or serverAuth or netscapeSGC or Microsoft SGC EKUs. 202 * 203 * @return true if there is no EKU extension or if any of the EKU extensions is one of the valid 204 * OIDs for web server certificates. 205 * 206 * TODO(palmer): This can be removed after the equivalent change is made to the Android default 207 * TrustManager and that change is shipped to a large majority of Android users. 208 */ 209 static boolean verifyKeyUsage(X509Certificate certificate) throws CertificateException { 210 List<String> ekuOids; 211 try { 212 ekuOids = certificate.getExtendedKeyUsage(); 213 } catch (NullPointerException e) { 214 // getExtendedKeyUsage() can crash due to an Android platform bug. This probably 215 // happens when the EKU extension data is malformed so return false here. 216 // See http://crbug.com/233610 217 return false; 218 } 219 if (ekuOids == null) 220 return true; 221 222 for (String ekuOid : ekuOids) { 223 if (ekuOid.equals(OID_TLS_SERVER_AUTH) || 224 ekuOid.equals(OID_ANY_EKU) || 225 ekuOid.equals(OID_SERVER_GATED_NETSCAPE) || 226 ekuOid.equals(OID_SERVER_GATED_MICROSOFT)) { 227 return true; 228 } 229 } 230 231 return false; 232 } 233 234 public static int verifyServerCertificates(byte[][] certChain, String authType) 235 throws KeyStoreException, NoSuchAlgorithmException { 236 if (certChain == null || certChain.length == 0 || certChain[0] == null) { 237 throw new IllegalArgumentException("Expected non-null and non-empty certificate " + 238 "chain passed as |certChain|. |certChain|=" + certChain); 239 } 240 241 try { 242 ensureInitialized(); 243 } catch (CertificateException e) { 244 return CertVerifyResultAndroid.VERIFY_FAILED; 245 } 246 247 X509Certificate[] serverCertificates = new X509Certificate[certChain.length]; 248 try { 249 for (int i = 0; i < certChain.length; ++i) { 250 serverCertificates[i] = createCertificateFromBytes(certChain[i]); 251 } 252 } catch (CertificateException e) { 253 return CertVerifyResultAndroid.VERIFY_UNABLE_TO_PARSE; 254 } 255 256 // Expired and not yet valid certificates would be rejected by the trust managers, but the 257 // trust managers report all certificate errors using the general CertificateException. In 258 // order to get more granular error information, cert validity time range is being checked 259 // separately. 260 try { 261 serverCertificates[0].checkValidity(); 262 if (!verifyKeyUsage(serverCertificates[0])) 263 return CertVerifyResultAndroid.VERIFY_INCORRECT_KEY_USAGE; 264 } catch (CertificateExpiredException e) { 265 return CertVerifyResultAndroid.VERIFY_EXPIRED; 266 } catch (CertificateNotYetValidException e) { 267 return CertVerifyResultAndroid.VERIFY_NOT_YET_VALID; 268 } catch (CertificateException e) { 269 return CertVerifyResultAndroid.VERIFY_FAILED; 270 } 271 272 synchronized (sLock) { 273 try { 274 sDefaultTrustManager.checkServerTrusted(serverCertificates, authType); 275 return CertVerifyResultAndroid.VERIFY_OK; 276 } catch (CertificateException eDefaultManager) { 277 try { 278 sTestTrustManager.checkServerTrusted(serverCertificates, authType); 279 return CertVerifyResultAndroid.VERIFY_OK; 280 } catch (CertificateException eTestManager) { 281 // Neither of the trust managers confirms the validity of the certificate chain, 282 // log the error message returned by the system trust manager. 283 Log.i(TAG, "Failed to validate the certificate chain, error: " + 284 eDefaultManager.getMessage()); 285 return CertVerifyResultAndroid.VERIFY_NO_TRUSTED_ROOT; 286 } 287 } 288 } 289 } 290 291 public static void setDisableCertificateObservationForTest(boolean disabled) { 292 sDisableCertificateObservationForTest = disabled; 293 } 294 /** 295 * Notify the native net::CertDatabase instance that the system database has been updated. 296 */ 297 private static native void nativeNotifyKeyChainChanged(); 298 299 /** 300 * Returns the application context. 301 */ 302 private static native Context nativeGetApplicationContext(); 303 304} 305