001    /*
002     * $HeadURL: http://juliusdavies.ca/svn/not-yet-commons-ssl/tags/commons-ssl-0.3.9/src/java/org/apache/commons/ssl/SSL.java $
003     * $Revision: 121 $
004     * $Date: 2007-11-13 21:26:57 -0800 (Tue, 13 Nov 2007) $
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    
032    package org.apache.commons.ssl;
033    
034    import javax.net.ssl.SSLContext;
035    import javax.net.ssl.SSLServerSocket;
036    import javax.net.ssl.SSLServerSocketFactory;
037    import javax.net.ssl.SSLSocket;
038    import javax.net.ssl.SSLSocketFactory;
039    import java.io.File;
040    import java.io.IOException;
041    import java.net.InetAddress;
042    import java.net.ServerSocket;
043    import java.net.Socket;
044    import java.net.UnknownHostException;
045    import java.security.GeneralSecurityException;
046    import java.security.KeyManagementException;
047    import java.security.KeyStoreException;
048    import java.security.NoSuchAlgorithmException;
049    import java.security.cert.CertificateException;
050    import java.security.cert.X509Certificate;
051    import java.util.ArrayList;
052    import java.util.Arrays;
053    import java.util.Collection;
054    import java.util.Collections;
055    import java.util.HashSet;
056    import java.util.Iterator;
057    import java.util.LinkedList;
058    import java.util.List;
059    import java.util.Properties;
060    import java.util.SortedSet;
061    import java.util.TreeSet;
062    
063    /**
064     * Not thread-safe.  (But who would ever share this thing across multiple
065     * threads???)
066     *
067     * @author Credit Union Central of British Columbia
068     * @author <a href="http://www.cucbc.com/">www.cucbc.com</a>
069     * @author <a href="mailto:juliusdavies@cucbc.com">juliusdavies@cucbc.com</a>
070     * @since May 1, 2006
071     */
072    public class SSL {
073        private final static String[] KNOWN_PROTOCOLS =
074            {"TLSv1", "SSLv3", "SSLv2", "SSLv2Hello"};
075    
076        // SUPPORTED_CIPHERS_ARRAY is initialized in the static constructor.
077        private final static String[] SUPPORTED_CIPHERS;
078    
079        public final static SortedSet KNOWN_PROTOCOLS_SET;
080        public final static SortedSet SUPPORTED_CIPHERS_SET;
081    
082        // RC4
083        public final static String SSL_RSA_WITH_RC4_128_SHA = "SSL_RSA_WITH_RC4_128_SHA";
084    
085        // 3DES
086        public final static String SSL_RSA_WITH_3DES_EDE_CBC_SHA = "SSL_RSA_WITH_3DES_EDE_CBC_SHA";
087        public final static String SSL_DHE_RSA_WITH_3DES_EDE_CBC_SHA = "SSL_DHE_RSA_WITH_3DES_EDE_CBC_SHA";
088        public final static String SSL_DHE_DSS_WITH_3DES_EDE_CBC_SHA = "SSL_DHE_DSS_WITH_3DES_EDE_CBC_SHA";
089    
090        // AES-128
091        public final static String TLS_RSA_WITH_AES_128_CBC_SHA = "TLS_RSA_WITH_AES_128_CBC_SHA";
092        public final static String TLS_DHE_RSA_WITH_AES_128_CBC_SHA = "TLS_DHE_RSA_WITH_AES_128_CBC_SHA";
093        public final static String TLS_DHE_DSS_WITH_AES_128_CBC_SHA = "TLS_DHE_DSS_WITH_AES_128_CBC_SHA";
094    
095        // AES-256
096        public final static String TLS_RSA_WITH_AES_256_CBC_SHA = "TLS_RSA_WITH_AES_256_CBC_SHA";
097        public final static String TLS_DHE_RSA_WITH_AES_256_CBC_SHA = "TLS_DHE_RSA_WITH_AES_256_CBC_SHA";
098        public final static String TLS_DHE_DSS_WITH_AES_256_CBC_SHA = "TLS_DHE_DSS_WITH_AES_256_CBC_SHA";
099    
100        static {
101            TreeSet ts = new TreeSet(Collections.reverseOrder());
102            ts.addAll(Arrays.asList(KNOWN_PROTOCOLS));
103            KNOWN_PROTOCOLS_SET = Collections.unmodifiableSortedSet(ts);
104    
105            // SSLSocketFactory.getDefault() sometimes blocks on FileInputStream
106            // reads of "/dev/random" (Linux only?).  You might find you system
107            // stuck here.  Move the mouse around a little!
108            SSLSocketFactory s = (SSLSocketFactory) SSLSocketFactory.getDefault();
109            ts = new TreeSet();
110            SUPPORTED_CIPHERS = s.getSupportedCipherSuites();
111            Arrays.sort(SUPPORTED_CIPHERS);
112            ts.addAll(Arrays.asList(SUPPORTED_CIPHERS));
113            SUPPORTED_CIPHERS_SET = Collections.unmodifiableSortedSet(ts);
114        }
115    
116        private Object sslContext = null;
117        private int initCount = 0;
118        private SSLSocketFactory socketFactory = null;
119        private SSLServerSocketFactory serverSocketFactory = null;
120        private HostnameVerifier hostnameVerifier = HostnameVerifier.DEFAULT;
121        private boolean checkHostname = true;
122        private final ArrayList allowedNames = new ArrayList();
123        private boolean checkCRL = true;
124        private boolean checkExpiry = true;
125        private boolean useClientMode = false;
126        private boolean useClientModeDefault = true;
127        private int soTimeout = 24 * 60 * 60 * 1000; // default: one day
128        private int connectTimeout = 60 * 60 * 1000; // default: one hour
129        private TrustChain trustChain = null;
130        private KeyMaterial keyMaterial = null;
131        private String[] enabledCiphers = null;
132        private String[] enabledProtocols = null;
133        private String defaultProtocol = "TLS";
134        private X509Certificate[] currentServerChain;
135        private X509Certificate[] currentClientChain;
136        private boolean wantClientAuth = true;
137        private boolean needClientAuth = false;
138        private SSLWrapperFactory sslWrapperFactory = SSLWrapperFactory.NO_WRAP;
139    
140        protected final boolean usingSystemProperties;
141    
142        public SSL()
143            throws GeneralSecurityException, IOException {
144            boolean usingSysProps = false;
145            Properties props = System.getProperties();
146            boolean ksSet = props.containsKey("javax.net.ssl.keyStore");
147            boolean tsSet = props.containsKey("javax.net.ssl.trustStore");
148            if (ksSet) {
149                String path = System.getProperty("javax.net.ssl.keyStore");
150                String pwd = System.getProperty("javax.net.ssl.keyStorePassword");
151                pwd = pwd != null ? pwd : ""; // JSSE default is "".
152                File f = new File(path);
153                if (f.exists()) {
154                    KeyMaterial km = new KeyMaterial(path, pwd.toCharArray());
155                    setKeyMaterial(km);
156                    usingSysProps = true;
157                }
158            }
159            boolean trustMaterialSet = false;
160            if (tsSet) {
161                String path = System.getProperty("javax.net.ssl.trustStore");
162                String pwd = System.getProperty("javax.net.ssl.trustStorePassword");
163                boolean pwdWasNull = pwd == null;
164                pwd = pwdWasNull ? "" : pwd; // JSSE default is "".
165                File f = new File(path);
166                if (f.exists()) {
167                    TrustMaterial tm;
168                    try {
169                        tm = new TrustMaterial(path, pwd.toCharArray());
170                    }
171                    catch (GeneralSecurityException gse) {
172                        // Probably a bad password.  If we're using the default password,
173                        // let's try and survive this setback.
174                        if (pwdWasNull) {
175                            tm = new TrustMaterial(path);
176                        } else {
177                            throw gse;
178                        }
179                    }
180    
181                    setTrustMaterial(tm);
182                    usingSysProps = true;
183                    trustMaterialSet = true;
184                }
185            }
186    
187            /*
188                No default trust material was set.  We'll use the JSSE standard way
189                where we test for "JSSE_CACERTS" first, and then fall back on
190                "CACERTS".  We could just leave TrustMaterial null, but then our
191                setCheckCRL() and setCheckExpiry() features won't work.  We need a
192                non-null TrustMaterial object in order to intercept and decorate
193                the JVM's default TrustManager.
194              */
195            if (!trustMaterialSet) {
196                setTrustMaterial(TrustMaterial.DEFAULT);
197            }
198            this.usingSystemProperties = usingSysProps;
199    
200            // By default we only use the strong ciphers (128 bit and higher).
201            // Consumers can call "useDefaultJavaCiphers()" to get the 40 and 56 bit
202            // ciphers back that Java normally has turned on.
203            useStrongCiphers();
204            dirtyAndReloadIfYoung();
205        }
206    
207        private void dirty() {
208            this.sslContext = null;
209            this.socketFactory = null;
210            this.serverSocketFactory = null;
211        }
212    
213        private void dirtyAndReloadIfYoung()
214            throws NoSuchAlgorithmException, KeyStoreException,
215            KeyManagementException, IOException, CertificateException {
216            dirty();
217            if (initCount >= 0 && initCount <= 5) {
218                // The first five init's we do early (before any sockets are
219                // created) in the hope that will trigger any explosions nice
220                // and early, with the correct exception type.
221    
222                // After the first five init's, we revert to a regular
223                // dirty / init pattern, and the "init" happens very late:
224                // just before the socket is created.  If badness happens, a
225                // wrapping RuntimeException will be thrown.
226                init();
227            }
228        }
229    
230        public SSLContext getSSLContext()
231            throws GeneralSecurityException, IOException
232    
233        {
234            Object obj = getSSLContextAsObject();
235            if (JavaImpl.isJava13()) {
236                try {
237                    return (SSLContext) obj;
238                }
239                catch (ClassCastException cce) {
240                    throw new ClassCastException("When using Java13 SSL, you must call SSL.getSSLContextAsObject() - " + cce);
241                }
242            }
243            return (SSLContext) obj;
244        }
245    
246        /**
247         * @return com.sun.net.ssl.SSLContext or javax.net.ssl.SSLContext depending
248         *         on the JSSE implementation we're using.
249         * @throws GeneralSecurityException problem creating SSLContext
250         * @throws IOException              problem creating SSLContext
251         */
252        public Object getSSLContextAsObject()
253            throws GeneralSecurityException, IOException
254    
255        {
256            if (sslContext == null) {
257                init();
258            }
259            return sslContext;
260        }
261    
262        public void addTrustMaterial(TrustChain trustChain)
263            throws NoSuchAlgorithmException, KeyStoreException,
264            KeyManagementException, IOException, CertificateException {
265            if (this.trustChain == null || trustChain == TrustMaterial.TRUST_ALL) {
266                this.trustChain = trustChain;
267            } else {
268                this.trustChain.addTrustMaterial(trustChain);
269            }
270            dirtyAndReloadIfYoung();
271        }
272    
273        public void setTrustMaterial(TrustChain trustChain)
274            throws NoSuchAlgorithmException, KeyStoreException,
275            KeyManagementException, IOException, CertificateException {
276            this.trustChain = trustChain;
277            dirtyAndReloadIfYoung();
278        }
279    
280        public void setKeyMaterial(KeyMaterial keyMaterial)
281            throws NoSuchAlgorithmException, KeyStoreException,
282            KeyManagementException, IOException, CertificateException {
283            this.keyMaterial = keyMaterial;
284            dirtyAndReloadIfYoung();
285        }
286    
287        public X509Certificate[] getAssociatedCertificateChain() {
288            if (keyMaterial != null) {
289                return keyMaterial.getAssociatedCertificateChain();
290            } else {
291                return null;
292            }
293        }
294    
295        public String[] getEnabledCiphers() {
296            return enabledCiphers != null ? enabledCiphers : getDefaultCipherSuites();
297        }
298    
299        public void useDefaultJavaCiphers() {
300            this.enabledCiphers = null;
301        }
302    
303        public void useStrongCiphers() {
304            LinkedList list = new LinkedList();
305            addCipher(list, SSL_DHE_DSS_WITH_3DES_EDE_CBC_SHA, false);
306            addCipher(list, SSL_DHE_RSA_WITH_3DES_EDE_CBC_SHA, false);
307            addCipher(list, SSL_RSA_WITH_3DES_EDE_CBC_SHA, false);
308            addCipher(list, SSL_RSA_WITH_RC4_128_SHA, false);
309            addCipher(list, TLS_DHE_DSS_WITH_AES_128_CBC_SHA, false);
310            addCipher(list, TLS_DHE_DSS_WITH_AES_256_CBC_SHA, false);
311            addCipher(list, TLS_DHE_RSA_WITH_AES_128_CBC_SHA, false);
312            addCipher(list, TLS_DHE_RSA_WITH_AES_256_CBC_SHA, false);
313            addCipher(list, TLS_RSA_WITH_AES_128_CBC_SHA, false);
314            addCipher(list, TLS_RSA_WITH_AES_256_CBC_SHA, false);
315            String[] strongCiphers = new String[list.size()];
316            list.toArray(strongCiphers);
317            String[] currentCiphers = getEnabledCiphers();
318            // Current ciphers must be default or something.  Odd that it's null,
319            // though.
320            if (currentCiphers == null) {
321                setEnabledCiphers(strongCiphers);
322            }
323    
324            Arrays.sort(strongCiphers);
325            Arrays.sort(currentCiphers);
326            // Let's only call "setEnabledCiphers" if our array is actually different
327            // than what's already set.
328            if (!Arrays.equals(strongCiphers, currentCiphers)) {
329                setEnabledCiphers(strongCiphers);
330            }
331        }
332    
333        public void setEnabledCiphers(String[] ciphers) {
334            HashSet desired = new HashSet(Arrays.asList(ciphers));
335            desired.removeAll(SUPPORTED_CIPHERS_SET);
336            if (!desired.isEmpty()) {
337                throw new IllegalArgumentException("following ciphers not supported: " + desired);
338            }
339            this.enabledCiphers = ciphers;
340        }
341    
342        public String[] getEnabledProtocols() {
343            return enabledProtocols != null ? enabledProtocols : KNOWN_PROTOCOLS;
344        }
345    
346        public void setEnabledProtocols(String[] protocols) {
347            HashSet desired = new HashSet(Arrays.asList(protocols));
348            desired.removeAll(KNOWN_PROTOCOLS_SET);
349            if (!desired.isEmpty()) {
350                throw new IllegalArgumentException("following protocols not supported: " + desired);
351            }
352            this.enabledProtocols = protocols;
353        }
354    
355        public String getDefaultProtocol() {
356            return defaultProtocol;
357        }
358    
359        public void setDefaultProtocol(String protocol) {
360            this.defaultProtocol = protocol;
361            dirty();
362        }
363    
364        public boolean getCheckHostname() {
365            return checkHostname;
366        }
367    
368        /**
369         * @return String[] array of alternate "allowed names" to try against a
370         *         server's x509 CN field if the host/ip we used didn't match.
371         *         Returns an empty list if there are no "allowedNames" currently
372         *         set.
373         */
374        public List getAllowedNames() {
375            return Collections.unmodifiableList(allowedNames);
376        }
377    
378        /**
379         * Offers a secure way to use virtual-hosting and SSL in some situations:
380         * for example you want to connect to "bar.com" but you know in advance
381         * that the SSL Certificate on that server only contains "CN=foo.com".  If
382         * you setAllowedNames( new String[] { "foo.com" } ) on your SSLClient in
383         * advance, you can connect securely, while still using "bar.com" as the
384         * host.
385         * <p/>
386         * Here's a code example using "cucbc.com" to connect, but anticipating
387         * "www.cucbc.com" in the server's certificate:
388         * <pre>
389         * SSLClient client = new SSLClient();
390         * client.setAllowedNames( new String[] { "www.cucbc.com" } );
391         * Socket s = client.createSocket( "cucbc.com", 443 );
392         * </pre>
393         * <p/>
394         * This technique is also useful if you don't want to use DNS, and want to
395         * connect using the IP address.
396         *
397         * @param allowedNames Collection of alternate "allowed names" to try against
398         *                     a server's x509 CN field if the host/ip we used didn't
399         *                     match.  Set to null to force strict matching against
400         *                     host/ip passed into createSocket().  Null is the
401         *                     default value.  Must be set in advance, before
402         *                     createSocket() is called.
403         */
404        public void addAllowedNames(Collection allowedNames) {
405            this.allowedNames.addAll(allowedNames);
406        }
407    
408        public void addAllowedName(String allowedName) {
409            this.allowedNames.add(allowedName);
410        }
411    
412        public void clearAllowedNames() {
413            this.allowedNames.clear();
414        }
415    
416        public void setCheckHostname(boolean checkHostname) {
417            this.checkHostname = checkHostname;
418        }
419    
420        public void setHostnameVerifier(HostnameVerifier verifier) {
421            if (verifier == null) {
422                verifier = HostnameVerifier.DEFAULT;
423            }
424            this.hostnameVerifier = verifier;
425        }
426    
427        public HostnameVerifier getHostnameVerifier() {
428            return hostnameVerifier;
429        }
430    
431        public boolean getCheckCRL() {
432            return checkCRL;
433        }
434    
435        public void setCheckCRL(boolean checkCRL) {
436            this.checkCRL = checkCRL;
437        }
438    
439        public boolean getCheckExpiry() {
440            return checkExpiry;
441        }
442    
443        public void setCheckExpiry(boolean checkExpiry) {
444            this.checkExpiry = checkExpiry;
445        }
446    
447        public void setSoTimeout(int soTimeout) {
448            if (soTimeout < 0) {
449                throw new IllegalArgumentException("soTimeout must not be negative");
450            }
451            this.soTimeout = soTimeout;
452        }
453    
454        public int getSoTimeout() {
455            return soTimeout;
456        }
457    
458        public void setConnectTimeout(int connectTimeout) {
459            if (connectTimeout < 0) {
460                throw new IllegalArgumentException("connectTimeout must not be negative");
461            }
462            this.connectTimeout = connectTimeout;
463        }
464    
465        public void setUseClientMode(boolean useClientMode) {
466            this.useClientModeDefault = false;
467            this.useClientMode = useClientMode;
468        }
469    
470        public boolean getUseClientModeDefault() {
471            return useClientModeDefault;
472        }
473    
474        public boolean getUseClientMode() {
475            return useClientMode;
476        }
477    
478        public void setWantClientAuth(boolean wantClientAuth) {
479            this.wantClientAuth = wantClientAuth;
480        }
481    
482        public void setNeedClientAuth(boolean needClientAuth) {
483            this.needClientAuth = needClientAuth;
484        }
485    
486        public boolean getWantClientAuth() {
487            return wantClientAuth;
488        }
489    
490        public boolean getNeedClientAuth() {
491            return needClientAuth;
492        }
493    
494        public SSLWrapperFactory getSSLWrapperFactory() {
495            return this.sslWrapperFactory;
496        }
497    
498        public void setSSLWrapperFactory(SSLWrapperFactory wf) {
499            this.sslWrapperFactory = wf;
500        }
501    
502        private void initThrowRuntime() {
503            try {
504                init();
505            }
506            catch (GeneralSecurityException gse) {
507                throw JavaImpl.newRuntimeException(gse);
508            }
509            catch (IOException ioe) {
510                throw JavaImpl.newRuntimeException(ioe);
511            }
512        }
513    
514        private void init()
515            throws NoSuchAlgorithmException, KeyStoreException,
516            KeyManagementException, IOException, CertificateException {
517            socketFactory = null;
518            serverSocketFactory = null;
519            this.sslContext = JavaImpl.init(this, trustChain, keyMaterial);
520            initCount++;
521        }
522    
523        public void doPreConnectSocketStuff(SSLSocket s)
524            throws IOException {
525            if (!useClientModeDefault) {
526                s.setUseClientMode(useClientMode);
527            }
528            if (soTimeout > 0) {
529                s.setSoTimeout(soTimeout);
530            }
531            if (enabledProtocols != null) {
532                JavaImpl.setEnabledProtocols(s, enabledProtocols);
533            }
534            if (enabledCiphers != null) {
535                s.setEnabledCipherSuites(enabledCiphers);
536            }
537        }
538    
539        public void doPostConnectSocketStuff(SSLSocket s, String host)
540            throws IOException {
541            if (checkHostname) {
542                int size = allowedNames.size() + 1;
543                String[] hosts = new String[size];
544                // hosts[ 0 ] MUST ALWAYS be the host given to createSocket().
545                hosts[0] = host;
546                int i = 1;
547                for (Iterator it = allowedNames.iterator(); it.hasNext(); i++) {
548                    hosts[i] = (String) it.next();
549                }
550                hostnameVerifier.check(hosts, s);
551            }
552        }
553    
554        public SSLSocket createSocket() throws IOException {
555            return sslWrapperFactory.wrap(JavaImpl.createSocket(this));
556        }
557    
558        /**
559         * Attempts to get a new socket connection to the given host within the
560         * given time limit.
561         *
562         * @param remoteHost the host name/IP
563         * @param remotePort the port on the host
564         * @param localHost  the local host name/IP to bind the socket to
565         * @param localPort  the port on the local machine
566         * @param timeout    the connection timeout (0==infinite)
567         * @return Socket a new socket
568         * @throws IOException          if an I/O error occurs while creating the socket
569         * @throws UnknownHostException if the IP address of the host cannot be
570         *                              determined
571         */
572        public Socket createSocket(String remoteHost, int remotePort,
573                                   InetAddress localHost, int localPort,
574                                   int timeout)
575            throws IOException {
576            // Only use our factory-wide connectTimeout if this method was passed
577            // in a timeout of 0 (infinite).
578            int factoryTimeout = getConnectTimeout();
579            int connectTimeout = timeout == 0 ? factoryTimeout : timeout;
580            SSLSocket s = JavaImpl.createSocket(this, remoteHost, remotePort,
581                localHost, localPort,
582                connectTimeout);
583            return sslWrapperFactory.wrap(s);
584        }
585    
586        public Socket createSocket(Socket s, String remoteHost, int remotePort,
587                                   boolean autoClose)
588            throws IOException {
589            SSLSocketFactory sf = getSSLSocketFactory();
590            s = sf.createSocket(s, remoteHost, remotePort, autoClose);
591            doPreConnectSocketStuff((SSLSocket) s);
592            doPostConnectSocketStuff((SSLSocket) s, remoteHost);
593            return sslWrapperFactory.wrap((SSLSocket) s);
594        }
595    
596        public ServerSocket createServerSocket() throws IOException {
597            SSLServerSocket ss = JavaImpl.createServerSocket(this);
598            return getSSLWrapperFactory().wrap(ss, this);
599        }
600    
601        /**
602         * Attempts to get a new socket connection to the given host within the
603         * given time limit.
604         *
605         * @param localHost the local host name/IP to bind against (null == ANY)
606         * @param port      the port to listen on
607         * @param backlog   number of connections allowed to queue up for accept().
608         * @return SSLServerSocket a new server socket
609         * @throws IOException if an I/O error occurs while creating thesocket
610         */
611        public ServerSocket createServerSocket(int port, int backlog,
612                                               InetAddress localHost)
613            throws IOException {
614            SSLServerSocketFactory f = getSSLServerSocketFactory();
615            ServerSocket ss = f.createServerSocket(port, backlog, localHost);
616            SSLServerSocket s = (SSLServerSocket) ss;
617            doPreConnectServerSocketStuff(s);
618            return getSSLWrapperFactory().wrap(s, this);
619        }
620    
621        public void doPreConnectServerSocketStuff(SSLServerSocket s)
622            throws IOException {
623            if (soTimeout > 0) {
624                s.setSoTimeout(soTimeout);
625            }
626            if (enabledProtocols != null) {
627                JavaImpl.setEnabledProtocols(s, enabledProtocols);
628            }
629            if (enabledCiphers != null) {
630                s.setEnabledCipherSuites(enabledCiphers);
631            }
632    
633            /*
634              setNeedClientAuth( false ) has an annoying side effect:  it seems to
635              reset setWantClient( true ) back to to false.  So I do things this
636              way to make sure setting things "true" happens after setting things
637              "false" - giving "true" priority.
638              */
639            if (!wantClientAuth) {
640                JavaImpl.setWantClientAuth(s, wantClientAuth);
641            }
642            if (!needClientAuth) {
643                s.setNeedClientAuth(needClientAuth);
644            }
645            if (wantClientAuth) {
646                JavaImpl.setWantClientAuth(s, wantClientAuth);
647            }
648            if (needClientAuth) {
649                s.setNeedClientAuth(needClientAuth);
650            }
651        }
652    
653        public SSLSocketFactory getSSLSocketFactory() {
654            if (sslContext == null) {
655                initThrowRuntime();
656            }
657            if (socketFactory == null) {
658                socketFactory = JavaImpl.getSSLSocketFactory(sslContext);
659            }
660            return socketFactory;
661        }
662    
663        public SSLServerSocketFactory getSSLServerSocketFactory() {
664            if (sslContext == null) {
665                initThrowRuntime();
666            }
667            if (serverSocketFactory == null) {
668                serverSocketFactory = JavaImpl.getSSLServerSocketFactory(sslContext);
669            }
670            return serverSocketFactory;
671        }
672    
673        public int getConnectTimeout() {
674            return connectTimeout;
675        }
676    
677        public String[] getDefaultCipherSuites() {
678            return getSSLSocketFactory().getDefaultCipherSuites();
679        }
680    
681        public String[] getSupportedCipherSuites() {
682            String[] s = new String[SUPPORTED_CIPHERS.length];
683            System.arraycopy(SUPPORTED_CIPHERS, 0, s, 0, s.length);
684            return s;
685        }
686    
687        public TrustChain getTrustChain() {
688            return trustChain;
689        }
690    
691        public void setCurrentServerChain(X509Certificate[] chain) {
692            this.currentServerChain = chain;
693        }
694    
695        public void setCurrentClientChain(X509Certificate[] chain) {
696            this.currentClientChain = chain;
697        }
698    
699        public X509Certificate[] getCurrentServerChain() {
700            return currentServerChain;
701        }
702    
703        public X509Certificate[] getCurrentClientChain() {
704            return currentClientChain;
705        }
706    
707        public static void main(String[] args) {
708            for (int i = 0; i < SUPPORTED_CIPHERS.length; i++) {
709                System.out.println(SUPPORTED_CIPHERS[i]);
710            }
711            System.out.println();
712            System.out.println("----------------------------------------------");
713            addCipher(null, SSL_DHE_DSS_WITH_3DES_EDE_CBC_SHA, true);
714            addCipher(null, SSL_DHE_RSA_WITH_3DES_EDE_CBC_SHA, true);
715            addCipher(null, SSL_RSA_WITH_3DES_EDE_CBC_SHA, true);
716            addCipher(null, SSL_RSA_WITH_RC4_128_SHA, true);
717            addCipher(null, TLS_DHE_DSS_WITH_AES_128_CBC_SHA, true);
718            addCipher(null, TLS_DHE_DSS_WITH_AES_256_CBC_SHA, true);
719            addCipher(null, TLS_DHE_RSA_WITH_AES_128_CBC_SHA, true);
720            addCipher(null, TLS_DHE_RSA_WITH_AES_256_CBC_SHA, true);
721            addCipher(null, TLS_RSA_WITH_AES_128_CBC_SHA, true);
722            addCipher(null, TLS_RSA_WITH_AES_256_CBC_SHA, true);
723        }
724    
725        private static void addCipher(List l, String c, boolean printOnStandardOut) {
726            boolean supported = false;
727            if (c != null && SUPPORTED_CIPHERS_SET.contains(c)) {
728                if (l != null) {
729                    l.add(c);
730                }
731                supported = true;
732            }
733            if (printOnStandardOut) {
734                System.out.println(c + ":\t" + supported);
735            }
736        }
737    
738    
739    }