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;
44import org.apache.harmony.xnet.provider.jsse.SSLParameters;
45
46/**
47 * SSLSocketFactory that provides optional (on debug devices, only) skipping of ssl certificfate
48 * chain validation and custom read timeouts used just when connecting to the server/negotiating
49 * an ssl session.
50 *
51 * You can skip the ssl certificate checking at runtime by setting socket.relaxsslcheck=yes on
52 * devices that do not have have ro.secure set.
53 */
54public class SSLCertificateSocketFactory extends SSLSocketFactory {
55
56    private static final String LOG_TAG = "SSLCertificateSocketFactory";
57
58    private static final TrustManager[] TRUST_MANAGER = new TrustManager[] {
59        new X509TrustManager() {
60            public X509Certificate[] getAcceptedIssuers() {
61                return null;
62            }
63
64            public void checkClientTrusted(X509Certificate[] certs,
65                    String authType) { }
66
67            public void checkServerTrusted(X509Certificate[] certs,
68                    String authType) { }
69        }
70    };
71
72    private final SSLSocketFactory mFactory;
73
74    private final int mSocketReadTimeoutForSslHandshake;
75
76    /**
77     * Do not use this constructor (will be deprecated).  Use {@link #getDefault(int)} instead.
78     */
79    public SSLCertificateSocketFactory(int socketReadTimeoutForSslHandshake)
80            throws NoSuchAlgorithmException, KeyManagementException {
81        this(socketReadTimeoutForSslHandshake, null /* cache */);
82    }
83
84    private SSLCertificateSocketFactory(int socketReadTimeoutForSslHandshake,
85            SSLClientSessionCache cache) throws NoSuchAlgorithmException, KeyManagementException {
86        SSLContextImpl sslContext = new SSLContextImpl();
87        sslContext.engineInit(null /* kms */,
88            TRUST_MANAGER, new java.security.SecureRandom(),
89            cache /* client cache */, null /* server cache */);
90        this.mFactory = sslContext.engineGetSocketFactory();
91        this.mSocketReadTimeoutForSslHandshake = socketReadTimeoutForSslHandshake;
92    }
93
94    /**
95     * Returns a new instance of a socket factory using the specified socket read
96     * timeout while connecting with the server/negotiating an ssl session.
97     *
98     * @param socketReadTimeoutForSslHandshake the socket read timeout used for performing
99     *        ssl handshake. The socket read timeout is set back to 0 after the handshake.
100     * @return a new SocketFactory, or null on error
101     */
102    public static SocketFactory getDefault(int socketReadTimeoutForSslHandshake) {
103        return getDefault(socketReadTimeoutForSslHandshake, null /* cache */);
104    }
105
106    /**
107     * Returns a new instance of a socket factory using the specified socket read
108     * timeout while connecting with the server/negotiating an ssl session.
109     * Persists ssl sessions using the provided {@link SSLClientSessionCache}.
110     *
111     * @param socketReadTimeoutForSslHandshake the socket read timeout used for performing
112     *        ssl handshake. The socket read timeout is set back to 0 after the handshake.
113     * @param cache The {@link SSLClientSessionCache} to use, if any.
114     * @return a new SocketFactory, or null on error
115     *
116     * @hide
117    */
118    public static SocketFactory getDefault(int socketReadTimeoutForSslHandshake,
119            SSLClientSessionCache cache) {
120        try {
121            return new SSLCertificateSocketFactory(socketReadTimeoutForSslHandshake, cache);
122        } catch (NoSuchAlgorithmException e) {
123            Log.e(LOG_TAG,
124                    "SSLCertifcateSocketFactory.getDefault" +
125                    " NoSuchAlgorithmException " , e);
126            return null;
127        } catch (KeyManagementException e) {
128            Log.e(LOG_TAG,
129                    "SSLCertifcateSocketFactory.getDefault" +
130                    " KeyManagementException " , e);
131            return null;
132        }
133    }
134
135    private boolean hasValidCertificateChain(Certificate[] certs)
136            throws IOException {
137        boolean trusted = (certs != null && (certs.length > 0));
138
139        if (trusted) {
140            try {
141                // the authtype we pass in doesn't actually matter
142                SSLParameters.getDefaultTrustManager()
143                        .checkServerTrusted((X509Certificate[]) certs, "RSA");
144            } catch (GeneralSecurityException e) {
145                String exceptionMessage = e != null ? e.getMessage() : "none";
146                if (Config.LOGD) {
147                    Log.d(LOG_TAG,"hasValidCertificateChain(): sec. exception: "
148                         + exceptionMessage);
149                }
150                trusted = false;
151            }
152        }
153
154        return trusted;
155    }
156
157    private void validateSocket(SSLSocket sslSock, String destHost)
158            throws IOException
159    {
160        if (Config.LOGV) {
161            Log.v(LOG_TAG,"validateSocket() to host "+destHost);
162        }
163
164        String relaxSslCheck = SystemProperties.get("socket.relaxsslcheck");
165        String secure = SystemProperties.get("ro.secure");
166
167        // only allow relaxing the ssl check on non-secure builds where the relaxation is
168        // specifically requested.
169        if ("0".equals(secure) && "yes".equals(relaxSslCheck)) {
170            if (Config.LOGD) {
171                Log.d(LOG_TAG,"sys prop socket.relaxsslcheck is set," +
172                        " ignoring invalid certs");
173            }
174            return;
175        }
176
177        Certificate[] certs = null;
178        sslSock.setUseClientMode(true);
179        sslSock.startHandshake();
180        certs = sslSock.getSession().getPeerCertificates();
181
182        // check that the root certificate in the chain belongs to
183        // a CA we trust
184        if (certs == null) {
185            Log.e(LOG_TAG,
186                    "[SSLCertificateSocketFactory] no trusted root CA");
187            throw new IOException("no trusted root CA");
188        }
189
190        if (Config.LOGV) {
191            Log.v(LOG_TAG,"validateSocket # certs = " +certs.length);
192        }
193
194        if (!hasValidCertificateChain(certs)) {
195            if (Config.LOGD) {
196                Log.d(LOG_TAG,"validateSocket(): certificate untrusted!");
197            }
198            throw new IOException("Certificate untrusted");
199        }
200
201        X509Certificate lastChainCert = (X509Certificate) certs[0];
202
203        if (!DomainNameChecker.match(lastChainCert, destHost)) {
204            if (Config.LOGD) {
205                Log.d(LOG_TAG,"validateSocket(): domain name check failed");
206            }
207            throw new IOException("Domain Name check failed");
208        }
209    }
210
211    public Socket createSocket(Socket socket, String s, int i, boolean flag)
212            throws IOException
213    {
214        throw new IOException("Cannot validate certification without a hostname");
215    }
216
217    public Socket createSocket(InetAddress inaddr, int i, InetAddress inaddr2, int j)
218            throws IOException
219    {
220        throw new IOException("Cannot validate certification without a hostname");
221    }
222
223    public Socket createSocket(InetAddress inaddr, int i) throws IOException {
224        throw new IOException("Cannot validate certification without a hostname");
225    }
226
227    public Socket createSocket(String s, int i, InetAddress inaddr, int j) throws IOException {
228        SSLSocket sslSock = (SSLSocket) mFactory.createSocket(s, i, inaddr, j);
229
230        if (mSocketReadTimeoutForSslHandshake >= 0) {
231            sslSock.setSoTimeout(mSocketReadTimeoutForSslHandshake);
232        }
233
234        validateSocket(sslSock,s);
235        sslSock.setSoTimeout(0);
236
237        return sslSock;
238    }
239
240    public Socket createSocket(String s, int i) throws IOException {
241        SSLSocket sslSock = (SSLSocket) mFactory.createSocket(s, i);
242
243        if (mSocketReadTimeoutForSslHandshake >= 0) {
244            sslSock.setSoTimeout(mSocketReadTimeoutForSslHandshake);
245        }
246
247        validateSocket(sslSock,s);
248        sslSock.setSoTimeout(0);
249
250        return sslSock;
251    }
252
253    public String[] getDefaultCipherSuites() {
254        return mFactory.getSupportedCipherSuites();
255    }
256
257    public String[] getSupportedCipherSuites() {
258        return mFactory.getSupportedCipherSuites();
259    }
260}
261
262
263