SSLCertificateSocketFactory.java revision 9066cfe9886ac131c34d59ed0e2d287b0e3c0087
1/* 2 * Copyright (C) 2008 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package android.net; 18 19import android.net.http.DomainNameChecker; 20import android.os.SystemProperties; 21import android.util.Config; 22import android.util.Log; 23 24import java.io.IOException; 25import java.net.InetAddress; 26import java.net.Socket; 27import java.security.GeneralSecurityException; 28import java.security.KeyManagementException; 29import java.security.KeyStore; 30import java.security.KeyStoreException; 31import java.security.NoSuchAlgorithmException; 32import java.security.cert.Certificate; 33import java.security.cert.X509Certificate; 34 35import javax.net.SocketFactory; 36import javax.net.ssl.SSLSocket; 37import javax.net.ssl.SSLSocketFactory; 38import javax.net.ssl.TrustManager; 39import javax.net.ssl.TrustManagerFactory; 40import javax.net.ssl.X509TrustManager; 41 42import org.apache.harmony.xnet.provider.jsse.SSLClientSessionCache; 43import org.apache.harmony.xnet.provider.jsse.SSLContextImpl; 44 45/** 46 * SSLSocketFactory that provides optional (on debug devices, only) skipping of ssl certificfate 47 * chain validation and custom read timeouts used just when connecting to the server/negotiating 48 * an ssl session. 49 * 50 * You can skip the ssl certificate checking at runtime by setting socket.relaxsslcheck=yes on 51 * devices that do not have have ro.secure set. 52 */ 53public class SSLCertificateSocketFactory extends SSLSocketFactory { 54 55 private static final String LOG_TAG = "SSLCertificateSocketFactory"; 56 57 private static X509TrustManager sDefaultTrustManager; 58 59 static { 60 try { 61 TrustManagerFactory tmf = TrustManagerFactory.getInstance("X509"); 62 tmf.init((KeyStore)null); 63 TrustManager[] tms = tmf.getTrustManagers(); 64 if (tms != null) { 65 for (TrustManager tm : tms) { 66 if (tm instanceof X509TrustManager) { 67 sDefaultTrustManager = (X509TrustManager)tm; 68 break; 69 } 70 } 71 } 72 } catch (NoSuchAlgorithmException e) { 73 Log.e(LOG_TAG, "Unable to get X509 Trust Manager ", e); 74 } catch (KeyStoreException e) { 75 Log.e(LOG_TAG, "Key Store exception while initializing TrustManagerFactory ", e); 76 } 77 } 78 79 private static final TrustManager[] TRUST_MANAGER = new TrustManager[] { 80 new X509TrustManager() { 81 public X509Certificate[] getAcceptedIssuers() { 82 return null; 83 } 84 85 public void checkClientTrusted(X509Certificate[] certs, 86 String authType) { } 87 88 public void checkServerTrusted(X509Certificate[] certs, 89 String authType) { } 90 } 91 }; 92 93 private final SSLSocketFactory mFactory; 94 95 private final int mSocketReadTimeoutForSslHandshake; 96 97 /** 98 * Do not use this constructor (will be deprecated). Use {@link #getDefault(int)} instead. 99 */ 100 public SSLCertificateSocketFactory(int socketReadTimeoutForSslHandshake) 101 throws NoSuchAlgorithmException, KeyManagementException { 102 this(socketReadTimeoutForSslHandshake, null /* cache */); 103 } 104 105 private SSLCertificateSocketFactory(int socketReadTimeoutForSslHandshake, 106 SSLClientSessionCache cache) throws NoSuchAlgorithmException, KeyManagementException { 107 SSLContextImpl sslContext = new SSLContextImpl(); 108 sslContext.engineInit(null /* kms */, 109 TRUST_MANAGER, new java.security.SecureRandom(), 110 cache /* client cache */, null /* server cache */); 111 this.mFactory = sslContext.engineGetSocketFactory(); 112 this.mSocketReadTimeoutForSslHandshake = socketReadTimeoutForSslHandshake; 113 } 114 115 /** 116 * Returns a new instance of a socket factory using the specified socket read 117 * timeout while connecting with the server/negotiating an ssl session. 118 * 119 * @param socketReadTimeoutForSslHandshake the socket read timeout used for performing 120 * ssl handshake. The socket read timeout is set back to 0 after the handshake. 121 * @return a new SocketFactory, or null on error 122 */ 123 public static SocketFactory getDefault(int socketReadTimeoutForSslHandshake) { 124 return getDefault(socketReadTimeoutForSslHandshake, null /* cache */); 125 } 126 127 /** 128 * Returns a new instance of a socket factory using the specified socket read 129 * timeout while connecting with the server/negotiating an ssl session. 130 * Persists ssl sessions using the provided {@link SSLClientSessionCache}. 131 * 132 * @param socketReadTimeoutForSslHandshake the socket read timeout used for performing 133 * ssl handshake. The socket read timeout is set back to 0 after the handshake. 134 * @param cache The {@link SSLClientSessionCache} to use, if any. 135 * @return a new SocketFactory, or null on error 136 * 137 * @hide 138 */ 139 public static SocketFactory getDefault(int socketReadTimeoutForSslHandshake, 140 SSLClientSessionCache cache) { 141 try { 142 return new SSLCertificateSocketFactory(socketReadTimeoutForSslHandshake, cache); 143 } catch (NoSuchAlgorithmException e) { 144 Log.e(LOG_TAG, 145 "SSLCertifcateSocketFactory.getDefault" + 146 " NoSuchAlgorithmException " , e); 147 return null; 148 } catch (KeyManagementException e) { 149 Log.e(LOG_TAG, 150 "SSLCertifcateSocketFactory.getDefault" + 151 " KeyManagementException " , e); 152 return null; 153 } 154 } 155 156 private boolean hasValidCertificateChain(Certificate[] certs) 157 throws IOException { 158 if (sDefaultTrustManager == null) { 159 if (Config.LOGD) { 160 Log.d(LOG_TAG,"hasValidCertificateChain():" + 161 " null default trust manager!"); 162 } 163 throw new IOException("null default trust manager"); 164 } 165 166 boolean trusted = (certs != null && (certs.length > 0)); 167 168 if (trusted) { 169 try { 170 // the authtype we pass in doesn't actually matter 171 sDefaultTrustManager.checkServerTrusted((X509Certificate[]) certs, "RSA"); 172 } catch (GeneralSecurityException e) { 173 String exceptionMessage = e != null ? e.getMessage() : "none"; 174 if (Config.LOGD) { 175 Log.d(LOG_TAG,"hasValidCertificateChain(): sec. exception: " 176 + exceptionMessage); 177 } 178 trusted = false; 179 } 180 } 181 182 return trusted; 183 } 184 185 private void validateSocket(SSLSocket sslSock, String destHost) 186 throws IOException 187 { 188 if (Config.LOGV) { 189 Log.v(LOG_TAG,"validateSocket() to host "+destHost); 190 } 191 192 String relaxSslCheck = SystemProperties.get("socket.relaxsslcheck"); 193 String secure = SystemProperties.get("ro.secure"); 194 195 // only allow relaxing the ssl check on non-secure builds where the relaxation is 196 // specifically requested. 197 if ("0".equals(secure) && "yes".equals(relaxSslCheck)) { 198 if (Config.LOGD) { 199 Log.d(LOG_TAG,"sys prop socket.relaxsslcheck is set," + 200 " ignoring invalid certs"); 201 } 202 return; 203 } 204 205 Certificate[] certs = null; 206 sslSock.setUseClientMode(true); 207 sslSock.startHandshake(); 208 certs = sslSock.getSession().getPeerCertificates(); 209 210 // check that the root certificate in the chain belongs to 211 // a CA we trust 212 if (certs == null) { 213 Log.e(LOG_TAG, 214 "[SSLCertificateSocketFactory] no trusted root CA"); 215 throw new IOException("no trusted root CA"); 216 } 217 218 if (Config.LOGV) { 219 Log.v(LOG_TAG,"validateSocket # certs = " +certs.length); 220 } 221 222 if (!hasValidCertificateChain(certs)) { 223 if (Config.LOGD) { 224 Log.d(LOG_TAG,"validateSocket(): certificate untrusted!"); 225 } 226 throw new IOException("Certificate untrusted"); 227 } 228 229 X509Certificate lastChainCert = (X509Certificate) certs[0]; 230 231 if (!DomainNameChecker.match(lastChainCert, destHost)) { 232 if (Config.LOGD) { 233 Log.d(LOG_TAG,"validateSocket(): domain name check failed"); 234 } 235 throw new IOException("Domain Name check failed"); 236 } 237 } 238 239 public Socket createSocket(Socket socket, String s, int i, boolean flag) 240 throws IOException 241 { 242 throw new IOException("Cannot validate certification without a hostname"); 243 } 244 245 public Socket createSocket(InetAddress inaddr, int i, InetAddress inaddr2, int j) 246 throws IOException 247 { 248 throw new IOException("Cannot validate certification without a hostname"); 249 } 250 251 public Socket createSocket(InetAddress inaddr, int i) throws IOException { 252 throw new IOException("Cannot validate certification without a hostname"); 253 } 254 255 public Socket createSocket(String s, int i, InetAddress inaddr, int j) throws IOException { 256 SSLSocket sslSock = (SSLSocket) mFactory.createSocket(s, i, inaddr, j); 257 258 if (mSocketReadTimeoutForSslHandshake >= 0) { 259 sslSock.setSoTimeout(mSocketReadTimeoutForSslHandshake); 260 } 261 262 validateSocket(sslSock,s); 263 sslSock.setSoTimeout(0); 264 265 return sslSock; 266 } 267 268 public Socket createSocket(String s, int i) throws IOException { 269 SSLSocket sslSock = (SSLSocket) mFactory.createSocket(s, i); 270 271 if (mSocketReadTimeoutForSslHandshake >= 0) { 272 sslSock.setSoTimeout(mSocketReadTimeoutForSslHandshake); 273 } 274 275 validateSocket(sslSock,s); 276 sslSock.setSoTimeout(0); 277 278 return sslSock; 279 } 280 281 public String[] getDefaultCipherSuites() { 282 return mFactory.getSupportedCipherSuites(); 283 } 284 285 public String[] getSupportedCipherSuites() { 286 return mFactory.getSupportedCipherSuites(); 287 } 288} 289 290 291