SSLCertificateSocketFactory.java revision 4ef6c9b6a16c9b65699705aaa64977fc60dd3331
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.os.SystemProperties;
20import android.util.Log;
21import java.io.IOException;
22import java.net.InetAddress;
23import java.net.Socket;
24import java.net.SocketException;
25import java.security.KeyManagementException;
26import java.security.cert.X509Certificate;
27import java.security.interfaces.ECPrivateKey;
28import javax.net.SocketFactory;
29import javax.net.ssl.HostnameVerifier;
30import javax.net.ssl.HttpsURLConnection;
31import javax.net.ssl.KeyManager;
32import javax.net.ssl.SSLException;
33import javax.net.ssl.SSLPeerUnverifiedException;
34import javax.net.ssl.SSLSession;
35import javax.net.ssl.SSLSocket;
36import javax.net.ssl.SSLSocketFactory;
37import javax.net.ssl.TrustManager;
38import javax.net.ssl.X509TrustManager;
39import org.apache.harmony.xnet.provider.jsse.OpenSSLContextImpl;
40import org.apache.harmony.xnet.provider.jsse.OpenSSLSocketImpl;
41import org.apache.harmony.xnet.provider.jsse.SSLClientSessionCache;
42
43/**
44 * SSLSocketFactory implementation with several extra features:
45 *
46 * <ul>
47 * <li>Timeout specification for SSL handshake operations
48 * <li>Hostname verification in most cases (see WARNINGs below)
49 * <li>Optional SSL session caching with {@link SSLSessionCache}
50 * <li>Optionally bypass all SSL certificate checks
51 * </ul>
52 *
53 * The handshake timeout does not apply to actual TCP socket connection.
54 * If you want a connection timeout as well, use {@link #createSocket()}
55 * and {@link Socket#connect(SocketAddress, int)}, after which you
56 * must verify the identity of the server you are connected to.
57 *
58 * <p class="caution"><b>Most {@link SSLSocketFactory} implementations do not
59 * verify the server's identity, allowing man-in-the-middle attacks.</b>
60 * This implementation does check the server's certificate hostname, but only
61 * for createSocket variants that specify a hostname.  When using methods that
62 * use {@link InetAddress} or which return an unconnected socket, you MUST
63 * verify the server's identity yourself to ensure a secure connection.</p>
64 *
65 * <p>One way to verify the server's identity is to use
66 * {@link HttpsURLConnection#getDefaultHostnameVerifier()} to get a
67 * {@link HostnameVerifier} to verify the certificate hostname.
68 *
69 * <p>On development devices, "setprop socket.relaxsslcheck yes" bypasses all
70 * SSL certificate and hostname checks for testing purposes.  This setting
71 * requires root access.
72 */
73public class SSLCertificateSocketFactory extends SSLSocketFactory {
74    private static final String TAG = "SSLCertificateSocketFactory";
75
76    private static final TrustManager[] INSECURE_TRUST_MANAGER = new TrustManager[] {
77        new X509TrustManager() {
78            public X509Certificate[] getAcceptedIssuers() { return null; }
79            public void checkClientTrusted(X509Certificate[] certs, String authType) { }
80            public void checkServerTrusted(X509Certificate[] certs, String authType) { }
81        }
82    };
83
84    private static final HostnameVerifier HOSTNAME_VERIFIER =
85        HttpsURLConnection.getDefaultHostnameVerifier();
86
87    private SSLSocketFactory mInsecureFactory = null;
88    private SSLSocketFactory mSecureFactory = null;
89    private TrustManager[] mTrustManagers = null;
90    private KeyManager[] mKeyManagers = null;
91    private byte[] mNpnProtocols = null;
92    private ECPrivateKey mChannelIdPrivateKey = null;
93
94    private final int mHandshakeTimeoutMillis;
95    private final SSLClientSessionCache mSessionCache;
96    private final boolean mSecure;
97
98    /** @deprecated Use {@link #getDefault(int)} instead. */
99    @Deprecated
100    public SSLCertificateSocketFactory(int handshakeTimeoutMillis) {
101        this(handshakeTimeoutMillis, null, true);
102    }
103
104    private SSLCertificateSocketFactory(
105            int handshakeTimeoutMillis, SSLSessionCache cache, boolean secure) {
106        mHandshakeTimeoutMillis = handshakeTimeoutMillis;
107        mSessionCache = cache == null ? null : cache.mSessionCache;
108        mSecure = secure;
109    }
110
111    /**
112     * Returns a new socket factory instance with an optional handshake timeout.
113     *
114     * @param handshakeTimeoutMillis to use for SSL connection handshake, or 0
115     *         for none.  The socket timeout is reset to 0 after the handshake.
116     * @return a new SSLSocketFactory with the specified parameters
117     */
118    public static SocketFactory getDefault(int handshakeTimeoutMillis) {
119        return new SSLCertificateSocketFactory(handshakeTimeoutMillis, null, true);
120    }
121
122    /**
123     * Returns a new socket factory instance with an optional handshake timeout
124     * and SSL session cache.
125     *
126     * @param handshakeTimeoutMillis to use for SSL connection handshake, or 0
127     *         for none.  The socket timeout is reset to 0 after the handshake.
128     * @param cache The {@link SSLSessionCache} to use, or null for no cache.
129     * @return a new SSLSocketFactory with the specified parameters
130     */
131    public static SSLSocketFactory getDefault(int handshakeTimeoutMillis, SSLSessionCache cache) {
132        return new SSLCertificateSocketFactory(handshakeTimeoutMillis, cache, true);
133    }
134
135    /**
136     * Returns a new instance of a socket factory with all SSL security checks
137     * disabled, using an optional handshake timeout and SSL session cache.
138     *
139     * <p class="caution"><b>Warning:</b> Sockets created using this factory
140     * are vulnerable to man-in-the-middle attacks!</p>
141     *
142     * @param handshakeTimeoutMillis to use for SSL connection handshake, or 0
143     *         for none.  The socket timeout is reset to 0 after the handshake.
144     * @param cache The {@link SSLSessionCache} to use, or null for no cache.
145     * @return an insecure SSLSocketFactory with the specified parameters
146     */
147    public static SSLSocketFactory getInsecure(int handshakeTimeoutMillis, SSLSessionCache cache) {
148        return new SSLCertificateSocketFactory(handshakeTimeoutMillis, cache, false);
149    }
150
151    /**
152     * Returns a socket factory (also named SSLSocketFactory, but in a different
153     * namespace) for use with the Apache HTTP stack.
154     *
155     * @param handshakeTimeoutMillis to use for SSL connection handshake, or 0
156     *         for none.  The socket timeout is reset to 0 after the handshake.
157     * @param cache The {@link SSLSessionCache} to use, or null for no cache.
158     * @return a new SocketFactory with the specified parameters
159     */
160    public static org.apache.http.conn.ssl.SSLSocketFactory getHttpSocketFactory(
161            int handshakeTimeoutMillis, SSLSessionCache cache) {
162        return new org.apache.http.conn.ssl.SSLSocketFactory(
163                new SSLCertificateSocketFactory(handshakeTimeoutMillis, cache, true));
164    }
165
166    /**
167     * Verify the hostname of the certificate used by the other end of a
168     * connected socket.  You MUST call this if you did not supply a hostname
169     * to {@link #createSocket()}.  It is harmless to call this method
170     * redundantly if the hostname has already been verified.
171     *
172     * <p>Wildcard certificates are allowed to verify any matching hostname,
173     * so "foo.bar.example.com" is verified if the peer has a certificate
174     * for "*.example.com".
175     *
176     * @param socket An SSL socket which has been connected to a server
177     * @param hostname The expected hostname of the remote server
178     * @throws IOException if something goes wrong handshaking with the server
179     * @throws SSLPeerUnverifiedException if the server cannot prove its identity
180     *
181     * @hide
182     */
183    public static void verifyHostname(Socket socket, String hostname) throws IOException {
184        if (!(socket instanceof SSLSocket)) {
185            throw new IllegalArgumentException("Attempt to verify non-SSL socket");
186        }
187
188        if (!isSslCheckRelaxed()) {
189            // The code at the start of OpenSSLSocketImpl.startHandshake()
190            // ensures that the call is idempotent, so we can safely call it.
191            SSLSocket ssl = (SSLSocket) socket;
192            ssl.startHandshake();
193
194            SSLSession session = ssl.getSession();
195            if (session == null) {
196                throw new SSLException("Cannot verify SSL socket without session");
197            }
198            if (!HOSTNAME_VERIFIER.verify(hostname, session)) {
199                throw new SSLPeerUnverifiedException("Cannot verify hostname: " + hostname);
200            }
201        }
202    }
203
204    private SSLSocketFactory makeSocketFactory(
205            KeyManager[] keyManagers, TrustManager[] trustManagers) {
206        try {
207            OpenSSLContextImpl sslContext = new OpenSSLContextImpl();
208            sslContext.engineInit(keyManagers, trustManagers, null);
209            sslContext.engineGetClientSessionContext().setPersistentCache(mSessionCache);
210            return sslContext.engineGetSocketFactory();
211        } catch (KeyManagementException e) {
212            Log.wtf(TAG, e);
213            return (SSLSocketFactory) SSLSocketFactory.getDefault();  // Fallback
214        }
215    }
216
217    private static boolean isSslCheckRelaxed() {
218        return "1".equals(SystemProperties.get("ro.debuggable")) &&
219            "yes".equals(SystemProperties.get("socket.relaxsslcheck"));
220    }
221
222    private synchronized SSLSocketFactory getDelegate() {
223        // Relax the SSL check if instructed (for this factory, or systemwide)
224        if (!mSecure || isSslCheckRelaxed()) {
225            if (mInsecureFactory == null) {
226                if (mSecure) {
227                    Log.w(TAG, "*** BYPASSING SSL SECURITY CHECKS (socket.relaxsslcheck=yes) ***");
228                } else {
229                    Log.w(TAG, "Bypassing SSL security checks at caller's request");
230                }
231                mInsecureFactory = makeSocketFactory(mKeyManagers, INSECURE_TRUST_MANAGER);
232            }
233            return mInsecureFactory;
234        } else {
235            if (mSecureFactory == null) {
236                mSecureFactory = makeSocketFactory(mKeyManagers, mTrustManagers);
237            }
238            return mSecureFactory;
239        }
240    }
241
242    /**
243     * Sets the {@link TrustManager}s to be used for connections made by this factory.
244     */
245    public void setTrustManagers(TrustManager[] trustManager) {
246        mTrustManagers = trustManager;
247
248        // Clear out all cached secure factories since configurations have changed.
249        mSecureFactory = null;
250        // Note - insecure factories only ever use the INSECURE_TRUST_MANAGER so they need not
251        // be cleared out here.
252    }
253
254    /**
255     * Sets the <a href="http://technotes.googlecode.com/git/nextprotoneg.html">Next
256     * Protocol Negotiation (NPN)</a> protocols that this peer is interested in.
257     *
258     * <p>For servers this is the sequence of protocols to advertise as
259     * supported, in order of preference. This list is sent unencrypted to
260     * all clients that support NPN.
261     *
262     * <p>For clients this is a list of supported protocols to match against the
263     * server's list. If there is no protocol supported by both client and
264     * server then the first protocol in the client's list will be selected.
265     * The order of the client's protocols is otherwise insignificant.
266     *
267     * @param npnProtocols a non-empty list of protocol byte arrays. All arrays
268     *     must be non-empty and of length less than 256.
269     */
270    public void setNpnProtocols(byte[][] npnProtocols) {
271        this.mNpnProtocols = toNpnProtocolsList(npnProtocols);
272    }
273
274    /**
275     * Returns an array containing the concatenation of length-prefixed byte
276     * strings.
277     */
278    static byte[] toNpnProtocolsList(byte[]... npnProtocols) {
279        if (npnProtocols.length == 0) {
280            throw new IllegalArgumentException("npnProtocols.length == 0");
281        }
282        int totalLength = 0;
283        for (byte[] s : npnProtocols) {
284            if (s.length == 0 || s.length > 255) {
285                throw new IllegalArgumentException("s.length == 0 || s.length > 255: " + s.length);
286            }
287            totalLength += 1 + s.length;
288        }
289        byte[] result = new byte[totalLength];
290        int pos = 0;
291        for (byte[] s : npnProtocols) {
292            result[pos++] = (byte) s.length;
293            for (byte b : s) {
294                result[pos++] = b;
295            }
296        }
297        return result;
298    }
299
300    /**
301     * Returns the <a href="http://technotes.googlecode.com/git/nextprotoneg.html">Next
302     * Protocol Negotiation (NPN)</a> protocol selected by client and server, or
303     * null if no protocol was negotiated.
304     *
305     * @param socket a socket created by this factory.
306     * @throws IllegalArgumentException if the socket was not created by this factory.
307     */
308    public byte[] getNpnSelectedProtocol(Socket socket) {
309        return castToOpenSSLSocket(socket).getNpnSelectedProtocol();
310    }
311
312    /**
313     * Sets the {@link KeyManager}s to be used for connections made by this factory.
314     */
315    public void setKeyManagers(KeyManager[] keyManagers) {
316        mKeyManagers = keyManagers;
317
318        // Clear out any existing cached factories since configurations have changed.
319        mSecureFactory = null;
320        mInsecureFactory = null;
321    }
322
323    /**
324     * Sets the {@link ECPrivateKey} to be used for TLS Channel ID by connections made by this
325     * factory.
326     *
327     * @param privateKey private key (enables TLS Channel ID) or {@code null} for no key (disables
328     *        TLS Channel ID). The private key has to be an Elliptic Curve (EC) key based on the
329     *        NIST P-256 curve (aka SECG secp256r1 or ANSI X9.62 prime256v1).
330     *
331     * @hide
332     */
333    public void setChannelIdPrivateKey(ECPrivateKey privateKey) {
334        mChannelIdPrivateKey = privateKey;
335    }
336
337    /**
338     * Enables <a href="http://tools.ietf.org/html/rfc5077#section-3.2">session ticket</a>
339     * support on the given socket.
340     *
341     * @param socket a socket created by this factory
342     * @param useSessionTickets {@code true} to enable session ticket support on this socket.
343     * @throws IllegalArgumentException if the socket was not created by this factory.
344     */
345    public void setUseSessionTickets(Socket socket, boolean useSessionTickets) {
346        castToOpenSSLSocket(socket).setUseSessionTickets(useSessionTickets);
347    }
348
349    /**
350     * Turns on <a href="http://tools.ietf.org/html/rfc6066#section-3">Server
351     * Name Indication (SNI)</a> on a given socket.
352     *
353     * @param socket a socket created by this factory.
354     * @param hostName the desired SNI hostname, null to disable.
355     * @throws IllegalArgumentException if the socket was not created by this factory.
356     */
357    public void setHostname(Socket socket, String hostName) {
358        castToOpenSSLSocket(socket).setHostname(hostName);
359    }
360
361    /**
362     * Sets this socket's SO_SNDTIMEO write timeout in milliseconds.
363     * Use 0 for no timeout.
364     * To take effect, this option must be set before the blocking method was called.
365     *
366     * @param socket a socket created by this factory.
367     * @param timeout the desired write timeout in milliseconds.
368     * @throws IllegalArgumentException if the socket was not created by this factory.
369     *
370     * @hide
371     */
372    public void setSoWriteTimeout(Socket socket, int writeTimeoutMilliseconds)
373            throws SocketException {
374        castToOpenSSLSocket(socket).setSoWriteTimeout(writeTimeoutMilliseconds);
375    }
376
377    private static OpenSSLSocketImpl castToOpenSSLSocket(Socket socket) {
378        if (!(socket instanceof OpenSSLSocketImpl)) {
379            throw new IllegalArgumentException("Socket not created by this factory: "
380                    + socket);
381        }
382
383        return (OpenSSLSocketImpl) socket;
384    }
385
386    /**
387     * {@inheritDoc}
388     *
389     * <p>This method verifies the peer's certificate hostname after connecting
390     * (unless created with {@link #getInsecure(int, SSLSessionCache)}).
391     */
392    @Override
393    public Socket createSocket(Socket k, String host, int port, boolean close) throws IOException {
394        OpenSSLSocketImpl s = (OpenSSLSocketImpl) getDelegate().createSocket(k, host, port, close);
395        s.setNpnProtocols(mNpnProtocols);
396        s.setHandshakeTimeout(mHandshakeTimeoutMillis);
397        s.setChannelIdPrivateKey(mChannelIdPrivateKey);
398        if (mSecure) {
399            verifyHostname(s, host);
400        }
401        return s;
402    }
403
404    /**
405     * Creates a new socket which is not connected to any remote host.
406     * You must use {@link Socket#connect} to connect the socket.
407     *
408     * <p class="caution"><b>Warning:</b> Hostname verification is not performed
409     * with this method.  You MUST verify the server's identity after connecting
410     * the socket to avoid man-in-the-middle attacks.</p>
411     */
412    @Override
413    public Socket createSocket() throws IOException {
414        OpenSSLSocketImpl s = (OpenSSLSocketImpl) getDelegate().createSocket();
415        s.setNpnProtocols(mNpnProtocols);
416        s.setHandshakeTimeout(mHandshakeTimeoutMillis);
417        s.setChannelIdPrivateKey(mChannelIdPrivateKey);
418        return s;
419    }
420
421    /**
422     * {@inheritDoc}
423     *
424     * <p class="caution"><b>Warning:</b> Hostname verification is not performed
425     * with this method.  You MUST verify the server's identity after connecting
426     * the socket to avoid man-in-the-middle attacks.</p>
427     */
428    @Override
429    public Socket createSocket(InetAddress addr, int port, InetAddress localAddr, int localPort)
430            throws IOException {
431        OpenSSLSocketImpl s = (OpenSSLSocketImpl) getDelegate().createSocket(
432                addr, port, localAddr, localPort);
433        s.setNpnProtocols(mNpnProtocols);
434        s.setHandshakeTimeout(mHandshakeTimeoutMillis);
435        s.setChannelIdPrivateKey(mChannelIdPrivateKey);
436        return s;
437    }
438
439    /**
440     * {@inheritDoc}
441     *
442     * <p class="caution"><b>Warning:</b> Hostname verification is not performed
443     * with this method.  You MUST verify the server's identity after connecting
444     * the socket to avoid man-in-the-middle attacks.</p>
445     */
446    @Override
447    public Socket createSocket(InetAddress addr, int port) throws IOException {
448        OpenSSLSocketImpl s = (OpenSSLSocketImpl) getDelegate().createSocket(addr, port);
449        s.setNpnProtocols(mNpnProtocols);
450        s.setHandshakeTimeout(mHandshakeTimeoutMillis);
451        s.setChannelIdPrivateKey(mChannelIdPrivateKey);
452        return s;
453    }
454
455    /**
456     * {@inheritDoc}
457     *
458     * <p>This method verifies the peer's certificate hostname after connecting
459     * (unless created with {@link #getInsecure(int, SSLSessionCache)}).
460     */
461    @Override
462    public Socket createSocket(String host, int port, InetAddress localAddr, int localPort)
463            throws IOException {
464        OpenSSLSocketImpl s = (OpenSSLSocketImpl) getDelegate().createSocket(
465                host, port, localAddr, localPort);
466        s.setNpnProtocols(mNpnProtocols);
467        s.setHandshakeTimeout(mHandshakeTimeoutMillis);
468        s.setChannelIdPrivateKey(mChannelIdPrivateKey);
469        if (mSecure) {
470            verifyHostname(s, host);
471        }
472        return s;
473    }
474
475    /**
476     * {@inheritDoc}
477     *
478     * <p>This method verifies the peer's certificate hostname after connecting
479     * (unless created with {@link #getInsecure(int, SSLSessionCache)}).
480     */
481    @Override
482    public Socket createSocket(String host, int port) throws IOException {
483        OpenSSLSocketImpl s = (OpenSSLSocketImpl) getDelegate().createSocket(host, port);
484        s.setNpnProtocols(mNpnProtocols);
485        s.setHandshakeTimeout(mHandshakeTimeoutMillis);
486        s.setChannelIdPrivateKey(mChannelIdPrivateKey);
487        if (mSecure) {
488            verifyHostname(s, host);
489        }
490        return s;
491    }
492
493    @Override
494    public String[] getDefaultCipherSuites() {
495        return getDelegate().getSupportedCipherSuites();
496    }
497
498    @Override
499    public String[] getSupportedCipherSuites() {
500        return getDelegate().getSupportedCipherSuites();
501    }
502}
503