SSLCertificateSocketFactory.java revision 3dec7d563a2f3e1eb967ce2054a00b6620e3558c
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.content.Context;
20import android.net.http.DomainNameChecker;
21import android.os.SystemProperties;
22import android.util.Config;
23import android.util.Log;
24
25import com.android.internal.net.SSLSessionCache;
26
27import java.io.IOException;
28import java.net.InetAddress;
29import java.net.Socket;
30import java.security.GeneralSecurityException;
31import java.security.KeyManagementException;
32import java.security.KeyStore;
33import java.security.KeyStoreException;
34import java.security.NoSuchAlgorithmException;
35import java.security.cert.Certificate;
36import java.security.cert.X509Certificate;
37
38import javax.net.SocketFactory;
39import javax.net.ssl.SSLSocket;
40import javax.net.ssl.SSLSocketFactory;
41import javax.net.ssl.TrustManager;
42import javax.net.ssl.TrustManagerFactory;
43import javax.net.ssl.X509TrustManager;
44
45/**
46 * SSLSocketFactory that allows skipping the certificate chain validation
47 * based on system setting (socket.relaxsslcheck=yes, ro.secure=1 - for
48 * testing only).
49 *
50 * It also adds a readTimeout that will be set on each created socket.
51 * The factory will use SSL session persistence if enabled by config.
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 static SSLSocketFactory factory;
94
95    /**
96     * Initialize a single default factory to be used for all returned
97     * sockets.
98     *
99     * Because of the signature of getDefault(int timeout) it needs to create
100     * a new instance which encapsulates the timeout on each call. We want
101     * to share a single SSLContext and SSLSessionCache.
102     *
103     * Can be called multiple times - but only the first will initialize the factory.
104     *
105     * @param androidContext will be used for SSL session persistence. Null for backward
106     * compatibility, no SSL persistence.
107     * @hide
108     */
109    public static synchronized void setupDefaultFactory(Context androidContext) {
110        if ( factory != null) {
111            // Can only be initialized once, to avoid having multiple caches.
112            return;
113        }
114        factory = SSLSessionCache.getSocketFactory(androidContext, TRUST_MANAGER);
115    }
116
117    private final int socketReadTimeoutForSslHandshake;
118
119    public SSLCertificateSocketFactory(int socketReadTimeoutForSslHandshake)
120            throws NoSuchAlgorithmException, KeyManagementException {
121        this.socketReadTimeoutForSslHandshake
122                = socketReadTimeoutForSslHandshake;
123    }
124
125    /**
126     * Returns a default instantiation of a new socket factory which
127     * only allows SSL connections with valid certificates.
128     *
129     * @param socketReadTimeoutForSslHandshake the socket read timeout used for performing
130     *        ssl handshake. The socket read timeout is set back to 0 after the handshake.
131     * @return a new SocketFactory, or null on error
132     */
133    public static SocketFactory getDefault(int socketReadTimeoutForSslHandshake) {
134        try {
135            if (factory == null) {
136                // The delegated factory was not initialized explicitely with a context.
137                // Use a default one.
138                setupDefaultFactory(null);
139            }
140            return new SSLCertificateSocketFactory(socketReadTimeoutForSslHandshake);
141        } catch (NoSuchAlgorithmException e) {
142            Log.e(LOG_TAG,
143                    "SSLCertifcateSocketFactory.getDefault" +
144                    " NoSuchAlgorithmException " , e);
145            return null;
146        } catch (KeyManagementException e) {
147            Log.e(LOG_TAG,
148                    "SSLCertifcateSocketFactory.getDefault" +
149                    " KeyManagementException " , e);
150            return null;
151        }
152    }
153
154    private boolean hasValidCertificateChain(Certificate[] certs)
155            throws IOException {
156        if (sDefaultTrustManager == null) {
157            if (Config.LOGD) {
158                Log.d(LOG_TAG,"hasValidCertificateChain():" +
159                          " null default trust manager!");
160            }
161            throw new IOException("null default trust manager");
162        }
163
164        boolean trusted = (certs != null && (certs.length > 0));
165
166        if (trusted) {
167            try {
168                // the authtype we pass in doesn't actually matter
169                sDefaultTrustManager.checkServerTrusted((X509Certificate[]) certs, "RSA");
170            } catch (GeneralSecurityException e) {
171                String exceptionMessage = e != null ? e.getMessage() : "none";
172                if (Config.LOGD) {
173                    Log.d(LOG_TAG,"hasValidCertificateChain(): sec. exception: "
174                         + exceptionMessage);
175                }
176                trusted = false;
177            }
178        }
179
180        return trusted;
181    }
182
183    private void validateSocket(SSLSocket sslSock, String destHost)
184            throws IOException
185    {
186        if (Config.LOGV) {
187            Log.v(LOG_TAG,"validateSocket() to host "+destHost);
188        }
189
190        String relaxSslCheck = SystemProperties.get("socket.relaxsslcheck");
191        String secure = SystemProperties.get("ro.secure");
192
193        // only allow relaxing the ssl check on non-secure builds where the relaxation is
194        // specifically requested.
195        if ("0".equals(secure) && "yes".equals(relaxSslCheck)) {
196            if (Config.LOGD) {
197                Log.d(LOG_TAG,"sys prop socket.relaxsslcheck is set," +
198                        " ignoring invalid certs");
199            }
200            return;
201        }
202
203        Certificate[] certs = null;
204        sslSock.setUseClientMode(true);
205        sslSock.startHandshake();
206        certs = sslSock.getSession().getPeerCertificates();
207
208        // check that the root certificate in the chain belongs to
209        // a CA we trust
210        if (certs == null) {
211            Log.e(LOG_TAG,
212                    "[SSLCertificateSocketFactory] no trusted root CA");
213            throw new IOException("no trusted root CA");
214        }
215
216        if (Config.LOGV) {
217            Log.v(LOG_TAG,"validateSocket # certs = " +certs.length);
218        }
219
220        if (!hasValidCertificateChain(certs)) {
221            if (Config.LOGD) {
222                Log.d(LOG_TAG,"validateSocket(): certificate untrusted!");
223            }
224            throw new IOException("Certificate untrusted");
225        }
226
227        X509Certificate lastChainCert = (X509Certificate) certs[0];
228
229        if (!DomainNameChecker.match(lastChainCert, destHost)) {
230            if (Config.LOGD) {
231                Log.d(LOG_TAG,"validateSocket(): domain name check failed");
232            }
233            throw new IOException("Domain Name check failed");
234        }
235    }
236
237    public Socket createSocket(Socket socket, String s, int i, boolean flag)
238            throws IOException
239    {
240        throw new IOException("Cannot validate certification without a hostname");
241    }
242
243    public Socket createSocket(InetAddress inaddr, int i, InetAddress inaddr2, int j)
244            throws IOException
245    {
246        throw new IOException("Cannot validate certification without a hostname");
247    }
248
249    public Socket createSocket(InetAddress inaddr, int i) throws IOException {
250        throw new IOException("Cannot validate certification without a hostname");
251    }
252
253    public Socket createSocket(String s, int i, InetAddress inaddr, int j) throws IOException {
254        SSLSocket sslSock = (SSLSocket) factory.createSocket(s, i, inaddr, j);
255
256        if (socketReadTimeoutForSslHandshake >= 0) {
257            sslSock.setSoTimeout(socketReadTimeoutForSslHandshake);
258        }
259
260        validateSocket(sslSock,s);
261        sslSock.setSoTimeout(0);
262
263        return sslSock;
264    }
265
266    public Socket createSocket(String s, int i) throws IOException {
267        SSLSocket sslSock = (SSLSocket) factory.createSocket(s, i);
268
269        if (socketReadTimeoutForSslHandshake >= 0) {
270            sslSock.setSoTimeout(socketReadTimeoutForSslHandshake);
271        }
272
273        validateSocket(sslSock,s);
274        sslSock.setSoTimeout(0);
275
276        return sslSock;
277    }
278
279    public String[] getDefaultCipherSuites() {
280        return factory.getSupportedCipherSuites();
281    }
282
283    public String[] getSupportedCipherSuites() {
284        return factory.getSupportedCipherSuites();
285    }
286}
287
288
289