001/*
002 * $HeadURL: file:///opt/dev/not-yet-commons-ssl-SVN-repo/tags/commons-ssl-0.3.17/src/java/org/apache/commons/ssl/SSL.java $
003 * $Revision: 180 $
004 * $Date: 2014-09-23 11:33:47 -0700 (Tue, 23 Sep 2014) $
005 *
006 * ====================================================================
007 * Licensed to the Apache Software Foundation (ASF) under one
008 * or more contributor license agreements.  See the NOTICE file
009 * distributed with this work for additional information
010 * regarding copyright ownership.  The ASF licenses this file
011 * to you under the Apache License, Version 2.0 (the
012 * "License"); you may not use this file except in compliance
013 * with the License.  You may obtain a copy of the License at
014 *
015 *   http://www.apache.org/licenses/LICENSE-2.0
016 *
017 * Unless required by applicable law or agreed to in writing,
018 * software distributed under the License is distributed on an
019 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
020 * KIND, either express or implied.  See the License for the
021 * specific language governing permissions and limitations
022 * under the License.
023 * ====================================================================
024 *
025 * This software consists of voluntary contributions made by many
026 * individuals on behalf of the Apache Software Foundation.  For more
027 * information on the Apache Software Foundation, please see
028 * <http://www.apache.org/>.
029 *
030 */
031
032package org.apache.commons.ssl;
033
034import javax.net.SocketFactory;
035import javax.net.ssl.*;
036import java.io.File;
037import java.io.IOException;
038import java.net.InetAddress;
039import java.net.ServerSocket;
040import java.net.Socket;
041import java.net.UnknownHostException;
042import java.security.GeneralSecurityException;
043import java.security.KeyManagementException;
044import java.security.KeyStoreException;
045import java.security.NoSuchAlgorithmException;
046import java.security.cert.CertificateException;
047import java.security.cert.X509Certificate;
048import java.util.*;
049
050/**
051 * Not thread-safe.  (But who would ever share this thing across multiple
052 * threads???)
053 *
054 * @author Credit Union Central of British Columbia
055 * @author <a href="http://www.cucbc.com/">www.cucbc.com</a>
056 * @author <a href="mailto:juliusdavies@cucbc.com">juliusdavies@cucbc.com</a>
057 * @since May 1, 2006
058 */
059public class SSL {
060    private final static String[] KNOWN_PROTOCOLS =
061            {"TLSv1.2", "TLSv1.1", "TLSv1", "SSLv3", "SSLv2", "SSLv2Hello"};
062
063    // SUPPORTED_CIPHERS_ARRAY is initialized in the static constructor.
064    private final static String[] SUPPORTED_CIPHERS;
065
066    public final static SortedSet KNOWN_PROTOCOLS_SET;
067    public final static SortedSet SUPPORTED_CIPHERS_SET;
068
069    static {
070        TreeSet<String> ts = new TreeSet<String>(Collections.reverseOrder());
071        ts.addAll(Arrays.asList(KNOWN_PROTOCOLS));
072        KNOWN_PROTOCOLS_SET = Collections.unmodifiableSortedSet(ts);
073
074        // SSLSocketFactory.getDefault() sometimes blocks on FileInputStream
075        // reads of "/dev/random" (Linux only?).  You might find you system
076        // stuck here.  Move the mouse around a little!
077        SSLSocketFactory s = (SSLSocketFactory) SSLSocketFactory.getDefault();
078        ts = new TreeSet<String>();
079        SUPPORTED_CIPHERS = s.getSupportedCipherSuites();
080        Arrays.sort(SUPPORTED_CIPHERS);
081        ts.addAll(Arrays.asList(SUPPORTED_CIPHERS));
082        SUPPORTED_CIPHERS_SET = Collections.unmodifiableSortedSet(ts);
083    }
084
085    private Object sslContext = null;
086    private int initCount = 0;
087    private SSLSocketFactory socketFactory = null;
088    private SSLServerSocketFactory serverSocketFactory = null;
089    private HostnameVerifier hostnameVerifier = HostnameVerifier.DEFAULT;
090    private boolean isSecure = true;  // if false, the client-style operations only create plain sockets.
091    private boolean checkHostname = true;
092    private boolean checkCRL = true;
093    private boolean checkExpiry = true;
094    private boolean useClientMode = false;
095    private boolean useClientModeDefault = true;
096    private int soTimeout = 24 * 60 * 60 * 1000; // default: one day
097    private int connectTimeout = 60 * 60 * 1000; // default: one hour
098    private TrustChain trustChain = null;
099    private KeyMaterial keyMaterial = null;
100    private String[] enabledCiphers = null;
101    private String[] enabledProtocols = null;
102    private String defaultProtocol = "TLS";
103    private X509Certificate[] currentServerChain;
104    private X509Certificate[] currentClientChain;
105    private boolean wantClientAuth = true;
106    private boolean needClientAuth = false;
107    private SSLWrapperFactory sslWrapperFactory = SSLWrapperFactory.NO_WRAP;
108    private Map dnsOverride;
109
110    protected final boolean usingSystemProperties;
111
112    public SSL()
113            throws GeneralSecurityException, IOException {
114        boolean usingSysProps = false;
115        Properties props = System.getProperties();
116        boolean ksSet = props.containsKey("javax.net.ssl.keyStore");
117        boolean tsSet = props.containsKey("javax.net.ssl.trustStore");
118        if (ksSet) {
119            String path = System.getProperty("javax.net.ssl.keyStore");
120            String pwd = System.getProperty("javax.net.ssl.keyStorePassword");
121            pwd = pwd != null ? pwd : ""; // JSSE default is "".
122            File f = new File(path);
123            if (f.exists()) {
124                KeyMaterial km = new KeyMaterial(path, pwd.toCharArray());
125                setKeyMaterial(km);
126                usingSysProps = true;
127            }
128        }
129        boolean trustMaterialSet = false;
130        if (tsSet) {
131            String path = System.getProperty("javax.net.ssl.trustStore");
132            String pwd = System.getProperty("javax.net.ssl.trustStorePassword");
133            boolean pwdWasNull = pwd == null;
134            pwd = pwdWasNull ? "" : pwd; // JSSE default is "".
135            File f = new File(path);
136            if (f.exists()) {
137                TrustMaterial tm;
138                try {
139                    tm = new TrustMaterial(path, pwd.toCharArray());
140                }
141                catch (GeneralSecurityException gse) {
142                    // Probably a bad password.  If we're using the default password,
143                    // let's try and survive this setback.
144                    if (pwdWasNull) {
145                        tm = new TrustMaterial(path);
146                    } else {
147                        throw gse;
148                    }
149                }
150
151                setTrustMaterial(tm);
152                usingSysProps = true;
153                trustMaterialSet = true;
154            }
155        }
156
157        /*
158            No default trust material was set.  We'll use the JSSE standard way
159            where we test for "JSSE_CACERTS" first, and then fall back on
160            "CACERTS".  We could just leave TrustMaterial null, but then our
161            setCheckCRL() and setCheckExpiry() features won't work.  We need a
162            non-null TrustMaterial object in order to intercept and decorate
163            the JVM's default TrustManager.
164          */
165        if (!trustMaterialSet) {
166            setTrustMaterial(TrustMaterial.DEFAULT);
167        }
168        this.usingSystemProperties = usingSysProps;
169        dirtyAndReloadIfYoung();
170    }
171
172    private void dirty() {
173        this.sslContext = null;
174        this.socketFactory = null;
175        this.serverSocketFactory = null;
176    }
177
178    private void dirtyAndReloadIfYoung()
179            throws NoSuchAlgorithmException, KeyStoreException,
180            KeyManagementException, IOException, CertificateException {
181        dirty();
182        if (initCount >= 0 && initCount <= 5) {
183            // The first five init's we do early (before any sockets are
184            // created) in the hope that will trigger any explosions nice
185            // and early, with the correct exception type.
186
187            // After the first five init's, we revert to a regular
188            // dirty / init pattern, and the "init" happens very late:
189            // just before the socket is created.  If badness happens, a
190            // wrapping RuntimeException will be thrown.
191            init();
192        }
193    }
194
195    String dnsOverride(String host) {
196        if (dnsOverride != null && dnsOverride.containsKey(host)) {
197            String override = (String) dnsOverride.get(host);
198            if (override != null && !"".equals(override.trim())) {
199                return override;
200            }
201        }
202        return host;
203    }
204
205    public void setDnsOverride(Map m) {
206        this.dnsOverride = m;
207    }
208
209    public void setIsSecure(boolean b) {
210        this.isSecure = b;
211    }
212
213    public boolean isSecure() {
214        return isSecure;
215    }
216
217    public SSLContext getSSLContext()
218            throws GeneralSecurityException, IOException
219
220    {
221        Object obj = getSSLContextAsObject();
222        if (JavaImpl.isJava13()) {
223            try {
224                return (SSLContext) obj;
225            }
226            catch (ClassCastException cce) {
227                throw new ClassCastException("When using Java13 SSL, you must call SSL.getSSLContextAsObject() - " + cce);
228            }
229        }
230        return (SSLContext) obj;
231    }
232
233    /**
234     * @return com.sun.net.ssl.SSLContext or javax.net.ssl.SSLContext depending
235     *         on the JSSE implementation we're using.
236     * @throws GeneralSecurityException problem creating SSLContext
237     * @throws IOException              problem creating SSLContext
238     */
239    public Object getSSLContextAsObject()
240            throws GeneralSecurityException, IOException
241
242    {
243        if (sslContext == null) {
244            init();
245        }
246        return sslContext;
247    }
248
249    public void addTrustMaterial(TrustChain trustChain)
250            throws NoSuchAlgorithmException, KeyStoreException,
251            KeyManagementException, IOException, CertificateException {
252        if (this.trustChain == null || trustChain == TrustMaterial.TRUST_ALL) {
253            this.trustChain = trustChain;
254        } else {
255            this.trustChain.addTrustMaterial(trustChain);
256        }
257        dirtyAndReloadIfYoung();
258    }
259
260    public void setTrustMaterial(TrustChain trustChain)
261            throws NoSuchAlgorithmException, KeyStoreException,
262            KeyManagementException, IOException, CertificateException {
263        this.trustChain = trustChain;
264        dirtyAndReloadIfYoung();
265    }
266
267    public void setKeyMaterial(KeyMaterial keyMaterial)
268            throws NoSuchAlgorithmException, KeyStoreException,
269            KeyManagementException, IOException, CertificateException {
270        this.keyMaterial = keyMaterial;
271        dirtyAndReloadIfYoung();
272    }
273
274    public X509Certificate[] getAssociatedCertificateChain() {
275        if (keyMaterial != null) {
276            List list = keyMaterial.getAssociatedCertificateChains();
277            return (X509Certificate[]) list.get(0);
278        } else {
279            return null;
280        }
281    }
282
283    public String[] getEnabledCiphers() {
284        return enabledCiphers != null ? enabledCiphers : getDefaultCipherSuites();
285    }
286
287    public void setEnabledCiphers(String[] ciphers) {
288        HashSet<String> desired = new HashSet<String>(Arrays.asList(ciphers));
289        desired.removeAll(SUPPORTED_CIPHERS_SET);
290        if (!desired.isEmpty()) {
291            throw new IllegalArgumentException("following ciphers not supported: " + desired);
292        }
293        this.enabledCiphers = ciphers;
294    }
295
296    public String[] getEnabledProtocols() {
297        return enabledProtocols;
298    }
299
300    public void setEnabledProtocols(String[] protocols) {
301        this.enabledProtocols = protocols;
302    }
303
304    public String getDefaultProtocol() {
305        return defaultProtocol;
306    }
307
308    public void setDefaultProtocol(String protocol) {
309        this.defaultProtocol = protocol;
310        dirty();
311    }
312
313    public boolean getCheckHostname() {
314        return checkHostname;
315    }
316
317    public void setCheckHostname(boolean checkHostname) {
318        this.checkHostname = checkHostname;
319    }
320
321    public void setHostnameVerifier(HostnameVerifier verifier) {
322        if (verifier == null) {
323            verifier = HostnameVerifier.DEFAULT;
324        }
325        this.hostnameVerifier = verifier;
326    }
327
328    public HostnameVerifier getHostnameVerifier() {
329        return hostnameVerifier;
330    }
331
332    public boolean getCheckCRL() {
333        return checkCRL;
334    }
335
336    public void setCheckCRL(boolean checkCRL) {
337        this.checkCRL = checkCRL;
338    }
339
340    public boolean getCheckExpiry() {
341        return checkExpiry;
342    }
343
344    public void setCheckExpiry(boolean checkExpiry) {
345        this.checkExpiry = checkExpiry;
346    }
347
348    public void setSoTimeout(int soTimeout) {
349        if (soTimeout < 0) {
350            throw new IllegalArgumentException("soTimeout must not be negative");
351        }
352        this.soTimeout = soTimeout;
353    }
354
355    public int getSoTimeout() {
356        return soTimeout;
357    }
358
359    public void setConnectTimeout(int connectTimeout) {
360        if (connectTimeout < 0) {
361            throw new IllegalArgumentException("connectTimeout must not be negative");
362        }
363        this.connectTimeout = connectTimeout;
364    }
365
366    public void setUseClientMode(boolean useClientMode) {
367        this.useClientModeDefault = false;
368        this.useClientMode = useClientMode;
369    }
370
371    public boolean getUseClientModeDefault() {
372        return useClientModeDefault;
373    }
374
375    public boolean getUseClientMode() {
376        return useClientMode;
377    }
378
379    public void setWantClientAuth(boolean wantClientAuth) {
380        this.wantClientAuth = wantClientAuth;
381    }
382
383    public void setNeedClientAuth(boolean needClientAuth) {
384        this.needClientAuth = needClientAuth;
385    }
386
387    public boolean getWantClientAuth() {
388        return wantClientAuth;
389    }
390
391    public boolean getNeedClientAuth() {
392        return needClientAuth;
393    }
394
395    public SSLWrapperFactory getSSLWrapperFactory() {
396        return this.sslWrapperFactory;
397    }
398
399    public void setSSLWrapperFactory(SSLWrapperFactory wf) {
400        this.sslWrapperFactory = wf;
401    }
402
403    private void initThrowRuntime() {
404        try {
405            init();
406        }
407        catch (GeneralSecurityException gse) {
408            throw JavaImpl.newRuntimeException(gse);
409        }
410        catch (IOException ioe) {
411            throw JavaImpl.newRuntimeException(ioe);
412        }
413    }
414
415    private void init()
416            throws NoSuchAlgorithmException, KeyStoreException,
417            KeyManagementException, IOException, CertificateException {
418        socketFactory = null;
419        serverSocketFactory = null;
420        this.sslContext = JavaImpl.init(this, trustChain, keyMaterial);
421        initCount++;
422    }
423
424    public void doPreConnectSocketStuff(Socket s) throws IOException {
425        if (s instanceof SSLSocket && !useClientModeDefault) {
426            ((SSLSocket) s).setUseClientMode(useClientMode);
427        }
428        if (soTimeout > 0) {
429            s.setSoTimeout(soTimeout);
430        }
431        if (s instanceof SSLSocket) {
432            if (enabledProtocols != null) {
433                JavaImpl.setEnabledProtocols(s, enabledProtocols);
434            }
435            if (enabledCiphers != null) {
436                ((SSLSocket) s).setEnabledCipherSuites(enabledCiphers);
437            }
438        }
439    }
440
441    public void doPostConnectSocketStuff(Socket s, String host)
442            throws IOException {
443        if (checkHostname && s instanceof SSLSocket) {
444            hostnameVerifier.check(host, (SSLSocket) s);
445        }
446    }
447
448    public Socket createSocket() throws IOException {
449        if (isSecure) {
450            return sslWrapperFactory.wrap(JavaImpl.createSocket(this));
451        } else {
452            Socket s = SocketFactory.getDefault().createSocket();
453            doPreConnectSocketStuff(s);
454            return s;
455        }
456    }
457
458    /**
459     * Attempts to get a new socket connection to the given host within the
460     * given time limit.
461     *
462     * @param remoteHost the host name/IP
463     * @param remotePort the port on the host
464     * @param localHost  the local host name/IP to bind the socket to
465     * @param localPort  the port on the local machine
466     * @param timeout    the connection timeout (0==infinite)
467     * @return Socket a new socket
468     * @throws IOException          if an I/O error occurs while creating the socket
469     * @throws UnknownHostException if the IP address of the host cannot be
470     *                              determined
471     */
472    public Socket createSocket(
473            String remoteHost, int remotePort, InetAddress localHost, int localPort, int timeout
474    ) throws IOException {
475        // Only use our factory-wide connectTimeout if this method was passed
476        // in a timeout of 0 (infinite).
477        int factoryTimeout = getConnectTimeout();
478        int connectTimeout = timeout == 0 ? factoryTimeout : timeout;
479        Socket s;
480        if (isSecure) {
481            s = JavaImpl.createSocket(
482                    this, remoteHost, remotePort, localHost, localPort, connectTimeout
483            );
484        } else {
485            s = JavaImpl.createPlainSocket(
486                    this, remoteHost, remotePort, localHost, localPort, connectTimeout
487            );
488        }
489        return sslWrapperFactory.wrap(s);
490    }
491
492    public Socket createSocket(
493            Socket s, String remoteHost, int remotePort, boolean autoClose
494    ) throws IOException {
495        SSLSocketFactory sf = getSSLSocketFactory();
496        s = sf.createSocket(s, remoteHost, remotePort, autoClose);
497        doPreConnectSocketStuff(s);
498        doPostConnectSocketStuff(s, remoteHost);
499        return sslWrapperFactory.wrap(s);
500    }
501
502    public ServerSocket createServerSocket() throws IOException {
503        SSLServerSocket ss = JavaImpl.createServerSocket(this);
504        return getSSLWrapperFactory().wrap(ss, this);
505    }
506
507    /**
508     * Attempts to get a new socket connection to the given host within the
509     * given time limit.
510     *
511     * @param localHost the local host name/IP to bind against (null == ANY)
512     * @param port      the port to listen on
513     * @param backlog   number of connections allowed to queue up for accept().
514     * @return SSLServerSocket a new server socket
515     * @throws IOException if an I/O error occurs while creating thesocket
516     */
517    public ServerSocket createServerSocket(int port, int backlog,
518                                           InetAddress localHost)
519            throws IOException {
520        SSLServerSocketFactory f = getSSLServerSocketFactory();
521        ServerSocket ss = f.createServerSocket(port, backlog, localHost);
522        SSLServerSocket s = (SSLServerSocket) ss;
523        doPreConnectServerSocketStuff(s);
524        return getSSLWrapperFactory().wrap(s, this);
525    }
526
527    public void doPreConnectServerSocketStuff(SSLServerSocket s)
528            throws IOException {
529        if (soTimeout > 0) {
530            s.setSoTimeout(soTimeout);
531        }
532        if (enabledProtocols != null) {
533            JavaImpl.setEnabledProtocols(s, enabledProtocols);
534        }
535        if (enabledCiphers != null) {
536            s.setEnabledCipherSuites(enabledCiphers);
537        }
538
539        /*
540          setNeedClientAuth( false ) has an annoying side effect:  it seems to
541          reset setWantClient( true ) back to to false.  So I do things this
542          way to make sure setting things "true" happens after setting things
543          "false" - giving "true" priority.
544          */
545        if (!wantClientAuth) {
546            JavaImpl.setWantClientAuth(s, false);
547        }
548        if (!needClientAuth) {
549            s.setNeedClientAuth(false);
550        }
551        if (wantClientAuth) {
552            JavaImpl.setWantClientAuth(s, true);
553        }
554        if (needClientAuth) {
555            s.setNeedClientAuth(true);
556        }
557    }
558
559    public SSLSocketFactory getSSLSocketFactory() {
560        if (sslContext == null) {
561            initThrowRuntime();
562        }
563        if (socketFactory == null) {
564            socketFactory = JavaImpl.getSSLSocketFactory(sslContext);
565        }
566        return socketFactory;
567    }
568
569    public SSLServerSocketFactory getSSLServerSocketFactory() {
570        if (sslContext == null) {
571            initThrowRuntime();
572        }
573        if (serverSocketFactory == null) {
574            serverSocketFactory = JavaImpl.getSSLServerSocketFactory(sslContext);
575        }
576        return serverSocketFactory;
577    }
578
579    public int getConnectTimeout() {
580        return connectTimeout;
581    }
582
583    public String[] getDefaultCipherSuites() {
584        return getSSLSocketFactory().getDefaultCipherSuites();
585    }
586
587    public String[] getSupportedCipherSuites() {
588        String[] s = new String[SUPPORTED_CIPHERS.length];
589        System.arraycopy(SUPPORTED_CIPHERS, 0, s, 0, s.length);
590        return s;
591    }
592
593    public TrustChain getTrustChain() {
594        return trustChain;
595    }
596
597    public void setCurrentServerChain(X509Certificate[] chain) {
598        this.currentServerChain = chain;
599    }
600
601    public void setCurrentClientChain(X509Certificate[] chain) {
602        this.currentClientChain = chain;
603    }
604
605    public X509Certificate[] getCurrentServerChain() {
606        return currentServerChain;
607    }
608
609    public X509Certificate[] getCurrentClientChain() {
610        return currentClientChain;
611    }
612}