OpenSSLSocketImpl.java revision aba5e8c281fb9c6be23229246473fa0b433dd997
1/*
2 * Copyright (C) 2007 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 org.apache.harmony.xnet.provider.jsse;
18
19import dalvik.system.BlockGuard;
20import dalvik.system.CloseGuard;
21import java.io.IOException;
22import java.io.InputStream;
23import java.io.OutputStream;
24import java.net.InetAddress;
25import java.net.Socket;
26import java.net.SocketException;
27import java.security.PrivateKey;
28import java.security.SecureRandom;
29import java.security.cert.CertificateEncodingException;
30import java.security.cert.CertificateException;
31import java.security.cert.X509Certificate;
32import java.util.ArrayList;
33import java.util.Arrays;
34import java.util.HashSet;
35import java.util.Set;
36import java.util.logging.Logger;
37import javax.net.ssl.HandshakeCompletedEvent;
38import javax.net.ssl.HandshakeCompletedListener;
39import javax.net.ssl.SSLException;
40import javax.net.ssl.SSLHandshakeException;
41import javax.net.ssl.SSLProtocolException;
42import javax.net.ssl.SSLSession;
43import javax.net.ssl.X509TrustManager;
44import javax.security.auth.x500.X500Principal;
45import org.apache.harmony.security.provider.cert.X509CertImpl;
46
47/**
48 * Implementation of the class OpenSSLSocketImpl based on OpenSSL.
49 * <p>
50 * This class only supports SSLv3 and TLSv1. This should be documented elsewhere
51 * later, for example in the package.html or a separate reference document.
52 * <p>
53 * Extensions to SSLSocket include:
54 * <ul>
55 * <li>handshake timeout
56 * <li>compression methods
57 * <li>session tickets
58 * <li>Server Name Indication
59 * </ul>
60 */
61public class OpenSSLSocketImpl
62        extends javax.net.ssl.SSLSocket
63        implements NativeCrypto.SSLHandshakeCallbacks {
64
65    private int sslNativePointer;
66    private InputStream is;
67    private OutputStream os;
68    private final Object handshakeLock = new Object();
69    private final Object readLock = new Object();
70    private final Object writeLock = new Object();
71    private SSLParametersImpl sslParameters;
72    private String[] enabledProtocols;
73    private String[] enabledCipherSuites;
74    private String[] enabledCompressionMethods;
75    private boolean useSessionTickets;
76    private String hostname;
77    private OpenSSLSessionImpl sslSession;
78    private final Socket socket;
79    private boolean autoClose;
80    private boolean handshakeStarted = false;
81    private final CloseGuard guard = CloseGuard.get();
82
83    /**
84     * Not set to true until the update from native that tells us the
85     * full handshake is complete, since SSL_do_handshake can return
86     * before the handshake is completely done due to
87     * handshake_cutthrough support.
88     */
89    private boolean handshakeCompleted = false;
90
91    private ArrayList<HandshakeCompletedListener> listeners;
92
93    /**
94     * Local cache of timeout to avoid getsockopt on every read and
95     * write for non-wrapped sockets. Note that
96     * OpenSSLSocketImplWrapper overrides setSoTimeout and
97     * getSoTimeout to delegate to the wrapped socket.
98     */
99    private int timeoutMilliseconds = 0;
100
101    private int handshakeTimeoutMilliseconds = -1;  // -1 = same as timeout; 0 = infinite
102    private String wrappedHost;
103    private int wrappedPort;
104
105    /**
106     * Class constructor with 1 parameter
107     *
108     * @param sslParameters Parameters for the SSL
109     *            context
110     * @throws IOException if network fails
111     */
112    protected OpenSSLSocketImpl(SSLParametersImpl sslParameters) throws IOException {
113        this.socket = this;
114        init(sslParameters);
115    }
116
117    /**
118     * Create an OpenSSLSocketImpl from an OpenSSLServerSocketImpl
119     *
120     * @param sslParameters Parameters for the SSL
121     *            context
122     * @throws IOException if network fails
123     */
124    protected OpenSSLSocketImpl(SSLParametersImpl sslParameters,
125                                String[] enabledProtocols,
126                                String[] enabledCipherSuites,
127                                String[] enabledCompressionMethods) throws IOException {
128        this.socket = this;
129        init(sslParameters, enabledProtocols, enabledCipherSuites, enabledCompressionMethods);
130    }
131
132    /**
133     * Class constructor with 3 parameters
134     *
135     * @throws IOException if network fails
136     * @throws java.net.UnknownHostException host not defined
137     */
138    protected OpenSSLSocketImpl(String host, int port, SSLParametersImpl sslParameters)
139            throws IOException {
140        super(host, port);
141        this.socket = this;
142        init(sslParameters);
143    }
144
145    /**
146     * Class constructor with 3 parameters: 1st is InetAddress
147     *
148     * @throws IOException if network fails
149     * @throws java.net.UnknownHostException host not defined
150     */
151    protected OpenSSLSocketImpl(InetAddress address, int port, SSLParametersImpl sslParameters)
152            throws IOException {
153        super(address, port);
154        this.socket = this;
155        init(sslParameters);
156    }
157
158
159    /**
160     * Class constructor with 5 parameters: 1st is host
161     *
162     * @throws IOException if network fails
163     * @throws java.net.UnknownHostException host not defined
164     */
165    protected OpenSSLSocketImpl(String host, int port,
166                                InetAddress clientAddress, int clientPort,
167                                SSLParametersImpl sslParameters)
168            throws IOException {
169        super(host, port, clientAddress, clientPort);
170        this.socket = this;
171        init(sslParameters);
172    }
173
174    /**
175     * Class constructor with 5 parameters: 1st is InetAddress
176     *
177     * @throws IOException if network fails
178     * @throws java.net.UnknownHostException host not defined
179     */
180    protected OpenSSLSocketImpl(InetAddress address, int port,
181                                InetAddress clientAddress, int clientPort,
182                                SSLParametersImpl sslParameters)
183            throws IOException {
184        super(address, port, clientAddress, clientPort);
185        this.socket = this;
186        init(sslParameters);
187    }
188
189    /**
190     * Constructor with 5 parameters: 1st is socket. Enhances an existing socket
191     * with SSL functionality. Invoked via OpenSSLSocketImplWrapper constructor.
192     *
193     * @throws IOException if network fails
194     */
195    protected OpenSSLSocketImpl(Socket socket, String host, int port,
196            boolean autoClose, SSLParametersImpl sslParameters) throws IOException {
197        this.socket = socket;
198        this.wrappedHost = host;
199        this.wrappedPort = port;
200        this.autoClose = autoClose;
201        init(sslParameters);
202
203        // this.timeout is not set intentionally.
204        // OpenSSLSocketImplWrapper.getSoTimeout will delegate timeout
205        // to wrapped socket
206    }
207
208    /**
209     * Initialize the SSL socket and set the certificates for the
210     * future handshaking.
211     */
212    private void init(SSLParametersImpl sslParameters) throws IOException {
213        init(sslParameters,
214             NativeCrypto.getSupportedProtocols(),
215             NativeCrypto.getDefaultCipherSuites(),
216             NativeCrypto.getDefaultCompressionMethods());
217    }
218
219    /**
220     * Initialize the SSL socket and set the certificates for the
221     * future handshaking.
222     */
223    private void init(SSLParametersImpl sslParameters,
224                      String[] enabledProtocols,
225                      String[] enabledCipherSuites,
226                      String[] enabledCompressionMethods) throws IOException {
227        this.sslParameters = sslParameters;
228        this.enabledProtocols = enabledProtocols;
229        this.enabledCipherSuites = enabledCipherSuites;
230        this.enabledCompressionMethods = enabledCompressionMethods;
231    }
232
233    /**
234     * Gets the suitable session reference from the session cache container.
235     *
236     * @return OpenSSLSessionImpl
237     */
238    private OpenSSLSessionImpl getCachedClientSession(ClientSessionContext sessionContext) {
239        if (super.getInetAddress() == null ||
240                super.getInetAddress().getHostAddress() == null ||
241                super.getInetAddress().getHostName() == null) {
242            return null;
243        }
244        OpenSSLSessionImpl session = (OpenSSLSessionImpl) sessionContext.getSession(
245                super.getInetAddress().getHostName(),
246                super.getPort());
247        if (session == null) {
248            return null;
249        }
250
251        String protocol = session.getProtocol();
252        boolean protocolFound = false;
253        for (String enabledProtocol : enabledProtocols) {
254            if (protocol.equals(enabledProtocol)) {
255                protocolFound = true;
256                break;
257            }
258        }
259        if (!protocolFound) {
260            return null;
261        }
262
263        String cipherSuite = session.getCipherSuite();
264        boolean cipherSuiteFound = false;
265        for (String enabledCipherSuite : enabledCipherSuites) {
266            if (cipherSuite.equals(enabledCipherSuite)) {
267                cipherSuiteFound = true;
268                break;
269            }
270        }
271        if (!cipherSuiteFound) {
272            return null;
273        }
274
275        String compressionMethod = session.getCompressionMethod();
276        boolean compressionMethodFound = false;
277        for (String enabledCompressionMethod : enabledCompressionMethods) {
278            if (compressionMethod.equals(enabledCompressionMethod)) {
279                compressionMethodFound = true;
280                break;
281            }
282        }
283        if (!compressionMethodFound) {
284            return null;
285        }
286
287        return session;
288    }
289
290    /**
291     * Ensures that logger is lazily loaded. The outer class seems to load
292     * before logging is ready.
293     */
294    static class LoggerHolder {
295        static final Logger logger = Logger.getLogger(OpenSSLSocketImpl.class.getName());
296    }
297
298    /**
299     * Starts a TLS/SSL handshake on this connection using some native methods
300     * from the OpenSSL library. It can negotiate new encryption keys, change
301     * cipher suites, or initiate a new session. The certificate chain is
302     * verified if the correspondent property in java.Security is set. All
303     * listeners are notified at the end of the TLS/SSL handshake.
304     *
305     * @throws <code>IOException</code> if network fails
306     */
307    @Override
308    public void startHandshake() throws IOException {
309        startHandshake(true);
310    }
311
312    /**
313     * Checks whether the socket is closed, and throws an exception.
314     *
315     * @throws SocketException
316     *             if the socket is closed.
317     */
318    private void checkOpen() throws SocketException {
319        if (isClosed()) {
320            throw new SocketException("Socket is closed");
321        }
322    }
323
324    /**
325     * Perform the handshake
326     * @param full If true, disable handshake cutthrough for a fully synchronous handshake
327     */
328    public synchronized void startHandshake(boolean full) throws IOException {
329        synchronized (handshakeLock) {
330            checkOpen();
331            if (!handshakeStarted) {
332                handshakeStarted = true;
333            } else {
334                return;
335            }
336        }
337
338        // note that this modifies the global seed, not something specific to the connection
339        final int seedLengthInBytes = NativeCrypto.RAND_SEED_LENGTH_IN_BYTES;
340        final SecureRandom secureRandom = sslParameters.getSecureRandomMember();
341        if (secureRandom == null) {
342            NativeCrypto.RAND_load_file("/dev/urandom", seedLengthInBytes);
343        } else {
344            NativeCrypto.RAND_seed(secureRandom.generateSeed(seedLengthInBytes));
345        }
346
347        final boolean client = sslParameters.getUseClientMode();
348
349        final int sslCtxNativePointer = (client) ?
350            sslParameters.getClientSessionContext().sslCtxNativePointer :
351            sslParameters.getServerSessionContext().sslCtxNativePointer;
352
353        this.sslNativePointer = 0;
354        boolean exception = true;
355        try {
356            sslNativePointer = NativeCrypto.SSL_new(sslCtxNativePointer);
357            guard.open("close");
358
359            // setup server certificates and private keys.
360            // clients will receive a call back to request certificates.
361            if (!client) {
362                Set<String> keyTypes = new HashSet<String>();
363                for (String enabledCipherSuite : enabledCipherSuites) {
364                    if (enabledCipherSuite.equals(NativeCrypto.TLS_EMPTY_RENEGOTIATION_INFO_SCSV)) {
365                        continue;
366                    }
367                    String keyType = CipherSuite.getByName(enabledCipherSuite).getServerKeyType();
368                    if (keyType != null) {
369                        keyTypes.add(keyType);
370                    }
371                }
372                for (String keyType : keyTypes) {
373                    try {
374                        setCertificate(sslParameters.getKeyManager().chooseServerAlias(keyType,
375                                                                                       null,
376                                                                                       this));
377                    } catch (CertificateEncodingException e) {
378                        throw new IOException(e);
379                    }
380                }
381            }
382
383            NativeCrypto.setEnabledProtocols(sslNativePointer, enabledProtocols);
384            NativeCrypto.setEnabledCipherSuites(sslNativePointer, enabledCipherSuites);
385            if (enabledCompressionMethods.length != 0) {
386                NativeCrypto.setEnabledCompressionMethods(sslNativePointer,
387                                                          enabledCompressionMethods);
388            }
389            if (useSessionTickets) {
390                NativeCrypto.SSL_clear_options(sslNativePointer, NativeCrypto.SSL_OP_NO_TICKET);
391            }
392            if (hostname != null) {
393                NativeCrypto.SSL_set_tlsext_host_name(sslNativePointer, hostname);
394            }
395
396            boolean enableSessionCreation = sslParameters.getEnableSessionCreation();
397            if (!enableSessionCreation) {
398                NativeCrypto.SSL_set_session_creation_enabled(sslNativePointer,
399                                                              enableSessionCreation);
400            }
401
402            AbstractSessionContext sessionContext;
403            OpenSSLSessionImpl session;
404            if (client) {
405                // look for client session to reuse
406                ClientSessionContext clientSessionContext = sslParameters.getClientSessionContext();
407                sessionContext = clientSessionContext;
408                session = getCachedClientSession(clientSessionContext);
409                if (session != null) {
410                    NativeCrypto.SSL_set_session(sslNativePointer,
411                                                 session.sslSessionNativePointer);
412                }
413            } else {
414                sessionContext = sslParameters.getServerSessionContext();
415                session = null;
416            }
417
418            // setup peer certificate verification
419            if (client) {
420                // TODO support for anonymous cipher would require us to
421                // conditionally use SSL_VERIFY_NONE
422            } else {
423                // needing client auth takes priority...
424                boolean certRequested = false;
425                if (sslParameters.getNeedClientAuth()) {
426                    NativeCrypto.SSL_set_verify(sslNativePointer,
427                                                NativeCrypto.SSL_VERIFY_PEER
428                                                | NativeCrypto.SSL_VERIFY_FAIL_IF_NO_PEER_CERT);
429                    certRequested = true;
430                // ... over just wanting it...
431                } else if (sslParameters.getWantClientAuth()) {
432                    NativeCrypto.SSL_set_verify(sslNativePointer,
433                                                NativeCrypto.SSL_VERIFY_PEER);
434                    certRequested = true;
435                // ... and it defaults properly so don't call SSL_set_verify in the common case.
436                } else {
437                    certRequested = false;
438                }
439
440                if (certRequested) {
441                    X509TrustManager trustManager = sslParameters.getTrustManager();
442                    X509Certificate[] issuers = trustManager.getAcceptedIssuers();
443                    if (issuers != null && issuers.length != 0) {
444                        byte[][] issuersBytes;
445                        try {
446                            issuersBytes = NativeCrypto.encodeIssuerX509Principals(issuers);
447                        } catch (CertificateEncodingException e) {
448                            throw new IOException("Problem encoding principals", e);
449                        }
450                        NativeCrypto.SSL_set_client_CA_list(sslNativePointer, issuersBytes);
451                    }
452                }
453            }
454
455            if (client && full) {
456                // we want to do a full synchronous handshake, so turn off cutthrough
457                NativeCrypto.SSL_clear_mode(sslNativePointer,
458                                            NativeCrypto.SSL_MODE_HANDSHAKE_CUTTHROUGH);
459            }
460
461            // Temporarily use a different timeout for the handshake process
462            int savedTimeoutMilliseconds = getSoTimeout();
463            if (handshakeTimeoutMilliseconds >= 0) {
464                setSoTimeout(handshakeTimeoutMilliseconds);
465            }
466
467            int sslSessionNativePointer;
468            try {
469                sslSessionNativePointer = NativeCrypto.SSL_do_handshake(sslNativePointer,
470                        socket.getFileDescriptor$(), this, getSoTimeout(), client);
471            } catch (CertificateException e) {
472                SSLHandshakeException wrapper = new SSLHandshakeException(e.getMessage());
473                wrapper.initCause(e);
474                throw wrapper;
475            }
476            byte[] sessionId = NativeCrypto.SSL_SESSION_session_id(sslSessionNativePointer);
477            sslSession = (OpenSSLSessionImpl) sessionContext.getSession(sessionId);
478            if (sslSession != null) {
479                sslSession.lastAccessedTime = System.currentTimeMillis();
480                LoggerHolder.logger.fine("Reused cached session for "
481                                         + getInetAddress() + ".");
482                NativeCrypto.SSL_SESSION_free(sslSessionNativePointer);
483            } else {
484                if (!enableSessionCreation) {
485                    // Should have been prevented by NativeCrypto.SSL_set_session_creation_enabled
486                    throw new IllegalStateException("SSL Session may not be created");
487                }
488                X509Certificate[] localCertificates
489                        = createCertChain(NativeCrypto.SSL_get_certificate(sslNativePointer));
490                X509Certificate[] peerCertificates
491                        = createCertChain(NativeCrypto.SSL_get_peer_cert_chain(sslNativePointer));
492                if (wrappedHost == null) {
493                    sslSession = new OpenSSLSessionImpl(sslSessionNativePointer,
494                                                        localCertificates, peerCertificates,
495                                                        super.getInetAddress().getHostName(),
496                                                        super.getPort(), sessionContext);
497                } else  {
498                    sslSession = new OpenSSLSessionImpl(sslSessionNativePointer,
499                                                        localCertificates, peerCertificates,
500                                                        wrappedHost, wrappedPort,
501                                                        sessionContext);
502                }
503                // if not, putSession later in handshakeCompleted() callback
504                if (handshakeCompleted) {
505                    sessionContext.putSession(sslSession);
506                }
507                LoggerHolder.logger.fine("Created new session for "
508                                         + getInetAddress().getHostName() + ".");
509            }
510
511            // Restore the original timeout now that the handshake is complete
512            if (handshakeTimeoutMilliseconds >= 0) {
513                setSoTimeout(savedTimeoutMilliseconds);
514            }
515
516            // if not, notifyHandshakeCompletedListeners later in handshakeCompleted() callback
517            if (handshakeCompleted) {
518                notifyHandshakeCompletedListeners();
519            }
520
521            exception = false;
522        } catch (SSLProtocolException e) {
523            throw new SSLHandshakeException(e);
524        } finally {
525            // on exceptional exit, treat the socket as closed
526            if (exception) {
527                close();
528            }
529        }
530    }
531
532    /**
533     * Return a possibly null array of X509Certificates given the
534     * possibly null array of DER encoded bytes.
535     */
536    private static X509Certificate[] createCertChain(byte[][] certificatesBytes) {
537        if (certificatesBytes == null) {
538            return null;
539        }
540        X509Certificate[] certificates = new X509Certificate[certificatesBytes.length];
541        for (int i = 0; i < certificatesBytes.length; i++) {
542            try {
543                certificates[i] = new X509CertImpl(certificatesBytes[i]);
544            } catch (IOException e) {
545                return null;
546            }
547        }
548        return certificates;
549    }
550
551    private void setCertificate(String alias) throws CertificateEncodingException, SSLException {
552        if (alias == null) {
553            return;
554        }
555        PrivateKey privateKey = sslParameters.getKeyManager().getPrivateKey(alias);
556        if (privateKey == null) {
557            return;
558        }
559        X509Certificate[] certificates = sslParameters.getKeyManager().getCertificateChain(alias);
560        if (certificates == null) {
561            return;
562        }
563
564        byte[] privateKeyBytes = privateKey.getEncoded();
565        byte[][] certificateBytes = NativeCrypto.encodeCertificates(certificates);
566        NativeCrypto.SSL_use_PrivateKey(sslNativePointer, privateKeyBytes);
567        NativeCrypto.SSL_use_certificate(sslNativePointer, certificateBytes);
568
569        // checks the last installed private key and certificate,
570        // so need to do this once per loop iteration
571        NativeCrypto.SSL_check_private_key(sslNativePointer);
572    }
573
574    /**
575     * Implementation of NativeCrypto.SSLHandshakeCallbacks
576     * invoked via JNI from client_cert_cb
577     */
578    public void clientCertificateRequested(byte[] keyTypeBytes, byte[][] asn1DerEncodedPrincipals)
579            throws CertificateEncodingException, SSLException {
580
581        String[] keyTypes = new String[keyTypeBytes.length];
582        for (int i = 0; i < keyTypeBytes.length; i++) {
583            keyTypes[i] = CipherSuite.getClientKeyType(keyTypeBytes[i]);
584        }
585
586        X500Principal[] issuers;
587        if (asn1DerEncodedPrincipals == null) {
588            issuers = null;
589        } else {
590            issuers = new X500Principal[asn1DerEncodedPrincipals.length];
591            for (int i = 0; i < asn1DerEncodedPrincipals.length; i++) {
592                issuers[i] = new X500Principal(asn1DerEncodedPrincipals[i]);
593            }
594        }
595        setCertificate(sslParameters.getKeyManager().chooseClientAlias(keyTypes, issuers, this));
596    }
597
598    /**
599     * Implementation of NativeCrypto.SSLHandshakeCallbacks
600     * invoked via JNI from info_callback
601     */
602    public void handshakeCompleted() {
603        handshakeCompleted = true;
604
605        // If sslSession is null, the handshake was completed during
606        // the call to NativeCrypto.SSL_do_handshake and not during a
607        // later read operation. That means we do not need to fixup
608        // the SSLSession and session cache or notify
609        // HandshakeCompletedListeners, it will be done in
610        // startHandshake.
611        if (sslSession == null) {
612            return;
613        }
614
615        // reset session id from the native pointer and update the
616        // appropriate cache.
617        sslSession.resetId();
618        AbstractSessionContext sessionContext =
619            (sslParameters.getUseClientMode())
620            ? sslParameters.getClientSessionContext()
621                : sslParameters.getServerSessionContext();
622        sessionContext.putSession(sslSession);
623
624        // let listeners know we are finally done
625        notifyHandshakeCompletedListeners();
626    }
627
628    private void notifyHandshakeCompletedListeners() {
629        if (listeners != null && !listeners.isEmpty()) {
630            // notify the listeners
631            HandshakeCompletedEvent event =
632                new HandshakeCompletedEvent(this, sslSession);
633            for (HandshakeCompletedListener listener : listeners) {
634                try {
635                    listener.handshakeCompleted(event);
636                } catch (RuntimeException e) {
637                    // The RI runs the handlers in a separate thread,
638                    // which we do not. But we try to preserve their
639                    // behavior of logging a problem and not killing
640                    // the handshaking thread just because a listener
641                    // has a problem.
642                    Thread thread = Thread.currentThread();
643                    thread.getUncaughtExceptionHandler().uncaughtException(thread, e);
644                }
645            }
646        }
647    }
648
649    /**
650     * Implementation of NativeCrypto.SSLHandshakeCallbacks
651     *
652     * @param bytes An array of ASN.1 DER encoded certficates
653     * @param authMethod auth algorithm name
654     *
655     * @throws CertificateException if the certificate is untrusted
656     */
657    @SuppressWarnings("unused")
658    public void verifyCertificateChain(byte[][] bytes, String authMethod)
659            throws CertificateException {
660        try {
661            if (bytes == null || bytes.length == 0) {
662                throw new SSLException("Peer sent no certificate");
663            }
664            X509Certificate[] peerCertificateChain = new X509Certificate[bytes.length];
665            for (int i = 0; i < bytes.length; i++) {
666                peerCertificateChain[i] = new X509CertImpl(bytes[i]);
667            }
668            boolean client = sslParameters.getUseClientMode();
669            if (client) {
670                sslParameters.getTrustManager().checkServerTrusted(peerCertificateChain,
671                                                                   authMethod);
672            } else {
673                String authType = peerCertificateChain[0].getPublicKey().getAlgorithm();
674                sslParameters.getTrustManager().checkClientTrusted(peerCertificateChain,
675                                                                   authType);
676            }
677
678        } catch (CertificateException e) {
679            throw e;
680        } catch (RuntimeException e) {
681            throw e;
682        } catch (Exception e) {
683            throw new RuntimeException(e);
684        }
685    }
686
687    /**
688     * Returns an input stream for this SSL socket using native calls to the
689     * OpenSSL library.
690     *
691     * @return: an input stream for reading bytes from this socket.
692     * @throws: <code>IOException</code> if an I/O error occurs when creating
693     *          the input stream, the socket is closed, the socket is not
694     *          connected, or the socket input has been shutdown.
695     */
696    @Override
697    public InputStream getInputStream() throws IOException {
698        checkOpen();
699        synchronized (this) {
700            if (is == null) {
701                is = new SSLInputStream();
702            }
703
704            return is;
705        }
706    }
707
708    /**
709     * Returns an output stream for this SSL socket using native calls to the
710     * OpenSSL library.
711     *
712     * @return an output stream for writing bytes to this socket.
713     * @throws <code>IOException</code> if an I/O error occurs when creating
714     *             the output stream, or no connection to the socket exists.
715     */
716    @Override
717    public OutputStream getOutputStream() throws IOException {
718        checkOpen();
719        synchronized (this) {
720            if (os == null) {
721                os = new SSLOutputStream();
722            }
723
724            return os;
725        }
726    }
727
728    /**
729     * This method is not supported for this SSLSocket implementation
730     * because reading from an SSLSocket may involve writing to the
731     * network.
732     */
733    @Override
734    public void shutdownInput() throws IOException {
735        throw new UnsupportedOperationException();
736    }
737
738    /**
739     * This method is not supported for this SSLSocket implementation
740     * because writing to an SSLSocket may involve reading from the
741     * network.
742     */
743    @Override
744    public void shutdownOutput() throws IOException {
745        throw new UnsupportedOperationException();
746    }
747
748    /**
749     * This inner class provides input data stream functionality
750     * for the OpenSSL native implementation. It is used to
751     * read data received via SSL protocol.
752     */
753    private class SSLInputStream extends InputStream {
754        SSLInputStream() throws IOException {
755            /**
756            /* Note: When startHandshake() throws an exception, no
757             * SSLInputStream object will be created.
758             */
759            OpenSSLSocketImpl.this.startHandshake(false);
760        }
761
762        /**
763         * Reads one byte. If there is no data in the underlying buffer,
764         * this operation can block until the data will be
765         * available.
766         * @return read value.
767         * @throws <code>IOException</code>
768         */
769        @Override
770        public int read() throws IOException {
771            BlockGuard.getThreadPolicy().onNetwork();
772            synchronized (readLock) {
773                checkOpen();
774                return NativeCrypto.SSL_read_byte(sslNativePointer, socket.getFileDescriptor$(),
775                        OpenSSLSocketImpl.this, getSoTimeout());
776            }
777        }
778
779        /**
780         * Method acts as described in spec for superclass.
781         * @see java.io.InputStream#read(byte[],int,int)
782         */
783        @Override
784        public int read(byte[] buf, int offset, int byteCount) throws IOException {
785            BlockGuard.getThreadPolicy().onNetwork();
786            synchronized (readLock) {
787                checkOpen();
788                Arrays.checkOffsetAndCount(buf.length, offset, byteCount);
789                if (byteCount == 0) {
790                    return 0;
791                }
792                return NativeCrypto.SSL_read(sslNativePointer, socket.getFileDescriptor$(),
793                        OpenSSLSocketImpl.this, buf, offset, byteCount, getSoTimeout());
794            }
795        }
796    }
797
798    /**
799     * This inner class provides output data stream functionality
800     * for the OpenSSL native implementation. It is used to
801     * write data according to the encryption parameters given in SSL context.
802     */
803    private class SSLOutputStream extends OutputStream {
804        SSLOutputStream() throws IOException {
805            /**
806            /* Note: When startHandshake() throws an exception, no
807             * SSLOutputStream object will be created.
808             */
809            OpenSSLSocketImpl.this.startHandshake(false);
810        }
811
812        /**
813         * Method acts as described in spec for superclass.
814         * @see java.io.OutputStream#write(int)
815         */
816        @Override
817        public void write(int b) throws IOException {
818            BlockGuard.getThreadPolicy().onNetwork();
819            synchronized (writeLock) {
820                checkOpen();
821                NativeCrypto.SSL_write_byte(sslNativePointer, socket.getFileDescriptor$(),
822                        OpenSSLSocketImpl.this, b);
823            }
824        }
825
826        /**
827         * Method acts as described in spec for superclass.
828         * @see java.io.OutputStream#write(byte[],int,int)
829         */
830        @Override
831        public void write(byte[] buf, int offset, int byteCount) throws IOException {
832            BlockGuard.getThreadPolicy().onNetwork();
833            synchronized (writeLock) {
834                checkOpen();
835                Arrays.checkOffsetAndCount(buf.length, offset, byteCount);
836                if (byteCount == 0) {
837                    return;
838                }
839                NativeCrypto.SSL_write(sslNativePointer, socket.getFileDescriptor$(),
840                        OpenSSLSocketImpl.this, buf, offset, byteCount);
841            }
842        }
843    }
844
845
846    /**
847     * The SSL session used by this connection is returned. The SSL session
848     * determines which cipher suite should be used by all connections within
849     * that session and which identities have the session's client and server.
850     * This method starts the SSL handshake.
851     * @return the SSLSession.
852     * @throws <code>IOException</code> if the handshake fails
853     */
854    @Override
855    public SSLSession getSession() {
856        if (sslSession == null) {
857            try {
858                startHandshake(true);
859            } catch (IOException e) {
860                // return an invalid session with
861                // invalid cipher suite of "SSL_NULL_WITH_NULL_NULL"
862                return SSLSessionImpl.NULL_SESSION;
863            }
864        }
865        return sslSession;
866    }
867
868    /**
869     * Registers a listener to be notified that a SSL handshake
870     * was successfully completed on this connection.
871     * @throws <code>IllegalArgumentException</code> if listener is null.
872     */
873    @Override
874    public void addHandshakeCompletedListener(
875            HandshakeCompletedListener listener) {
876        if (listener == null) {
877            throw new IllegalArgumentException("Provided listener is null");
878        }
879        if (listeners == null) {
880            listeners = new ArrayList();
881        }
882        listeners.add(listener);
883    }
884
885    /**
886     * The method removes a registered listener.
887     * @throws IllegalArgumentException if listener is null or not registered
888     */
889    @Override
890    public void removeHandshakeCompletedListener(
891            HandshakeCompletedListener listener) {
892        if (listener == null) {
893            throw new IllegalArgumentException("Provided listener is null");
894        }
895        if (listeners == null) {
896            throw new IllegalArgumentException(
897                    "Provided listener is not registered");
898        }
899        if (!listeners.remove(listener)) {
900            throw new IllegalArgumentException(
901                    "Provided listener is not registered");
902        }
903    }
904
905    /**
906     * Returns true if new SSL sessions may be established by this socket.
907     *
908     * @return true if the session may be created; false if a session already
909     *         exists and must be resumed.
910     */
911    @Override
912    public boolean getEnableSessionCreation() {
913        return sslParameters.getEnableSessionCreation();
914    }
915
916    /**
917     * Set a flag for the socket to inhibit or to allow the creation of a new
918     * SSL sessions. If the flag is set to false, and there are no actual
919     * sessions to resume, then there will be no successful handshaking.
920     *
921     * @param flag true if session may be created; false
922     *            if a session already exists and must be resumed.
923     */
924    @Override
925    public void setEnableSessionCreation(boolean flag) {
926        sslParameters.setEnableSessionCreation(flag);
927    }
928
929    /**
930     * The names of the cipher suites which could be used by the SSL connection
931     * are returned.
932     * @return an array of cipher suite names
933     */
934    @Override
935    public String[] getSupportedCipherSuites() {
936        return NativeCrypto.getSupportedCipherSuites();
937    }
938
939    /**
940     * The names of the cipher suites that are in use in the actual the SSL
941     * connection are returned.
942     *
943     * @return an array of cipher suite names
944     */
945    @Override
946    public String[] getEnabledCipherSuites() {
947        return enabledCipherSuites.clone();
948    }
949
950    /**
951     * This method enables the cipher suites listed by
952     * getSupportedCipherSuites().
953     *
954     * @param suites names of all the cipher suites to
955     *            put on use
956     * @throws IllegalArgumentException when one or more of the
957     *             ciphers in array suites are not supported, or when the array
958     *             is null.
959     */
960    @Override
961    public void setEnabledCipherSuites(String[] suites) {
962        enabledCipherSuites = NativeCrypto.checkEnabledCipherSuites(suites);
963    }
964
965    /**
966     * The names of the protocols' versions that may be used on this SSL
967     * connection.
968     * @return an array of protocols names
969     */
970    @Override
971    public String[] getSupportedProtocols() {
972        return NativeCrypto.getSupportedProtocols();
973    }
974
975    /**
976     * The names of the protocols' versions that are in use on this SSL
977     * connection.
978     *
979     * @return an array of protocols names
980     */
981    @Override
982    public String[] getEnabledProtocols() {
983        return enabledProtocols.clone();
984    }
985
986    /**
987     * This method enables the protocols' versions listed by
988     * getSupportedProtocols().
989     *
990     * @param protocols The names of all the protocols to allow
991     *
992     * @throws IllegalArgumentException when one or more of the names in the
993     *             array are not supported, or when the array is null.
994     */
995    @Override
996    public void setEnabledProtocols(String[] protocols) {
997        enabledProtocols = NativeCrypto.checkEnabledProtocols(protocols);
998    }
999
1000    /**
1001     * The names of the compression methods that may be used on this SSL
1002     * connection.
1003     * @return an array of compression methods
1004     */
1005    public String[] getSupportedCompressionMethods() {
1006        return NativeCrypto.getSupportedCompressionMethods();
1007    }
1008
1009    /**
1010     * The names of the compression methods versions that are in use
1011     * on this SSL connection.
1012     *
1013     * @return an array of compression methods
1014     */
1015    public String[] getEnabledCompressionMethods() {
1016        return enabledCompressionMethods.clone();
1017    }
1018
1019    /**
1020     * This method enables the compression method listed by
1021     * getSupportedCompressionMethods().
1022     *
1023     * @param methods The names of all the compression methods to allow
1024     *
1025     * @throws IllegalArgumentException when one or more of the names in the
1026     *             array are not supported, or when the array is null.
1027     */
1028    public void setEnabledCompressionMethods (String[] methods) {
1029        enabledCompressionMethods = NativeCrypto.checkEnabledCompressionMethods(methods);
1030    }
1031
1032    /**
1033     * This method enables session ticket support.
1034     *
1035     * @param useSessionTickets True to enable session tickets
1036     */
1037    public void setUseSessionTickets(boolean useSessionTickets) {
1038        this.useSessionTickets = useSessionTickets;
1039    }
1040
1041    /**
1042     * This method gives true back if the SSL socket is set to client mode.
1043     *
1044     * @return true if the socket should do the handshaking as client.
1045     */
1046    public boolean getUseSessionTickets() {
1047        return useSessionTickets;
1048    }
1049
1050    /**
1051     * This method enables Server Name Indication
1052     *
1053     * @param hostname the desired SNI hostname, or null to disable
1054     */
1055    public void setHostname(String hostname) {
1056        this.hostname = hostname;
1057    }
1058
1059    /**
1060     * This method returns the current SNI hostname
1061     *
1062     * @return a host name if SNI is enabled, or null otherwise
1063     */
1064    public String getHostname() {
1065        return hostname;
1066    }
1067
1068    /**
1069     * This method gives true back if the SSL socket is set to client mode.
1070     *
1071     * @return true if the socket should do the handshaking as client.
1072     */
1073    public boolean getUseClientMode() {
1074        return sslParameters.getUseClientMode();
1075    }
1076
1077    /**
1078     * This method set the actual SSL socket to client mode.
1079     *
1080     * @param mode true if the socket starts in client
1081     *            mode
1082     * @throws IllegalArgumentException if mode changes during
1083     *             handshake.
1084     */
1085    @Override
1086    public void setUseClientMode(boolean mode) {
1087        if (handshakeStarted) {
1088            throw new IllegalArgumentException(
1089            "Could not change the mode after the initial handshake has begun.");
1090        }
1091        sslParameters.setUseClientMode(mode);
1092    }
1093
1094    /**
1095     * Returns true if the SSL socket requests client's authentication. Relevant
1096     * only for server sockets!
1097     *
1098     * @return true if client authentication is desired, false if not.
1099     */
1100    @Override
1101    public boolean getWantClientAuth() {
1102        return sslParameters.getWantClientAuth();
1103    }
1104
1105    /**
1106     * Returns true if the SSL socket needs client's authentication. Relevant
1107     * only for server sockets!
1108     *
1109     * @return true if client authentication is desired, false if not.
1110     */
1111    @Override
1112    public boolean getNeedClientAuth() {
1113        return sslParameters.getNeedClientAuth();
1114    }
1115
1116    /**
1117     * Sets the SSL socket to use client's authentication. Relevant only for
1118     * server sockets!
1119     *
1120     * @param need true if client authentication is
1121     *            desired, false if not.
1122     */
1123    @Override
1124    public void setNeedClientAuth(boolean need) {
1125        sslParameters.setNeedClientAuth(need);
1126    }
1127
1128    /**
1129     * Sets the SSL socket to use client's authentication. Relevant only for
1130     * server sockets! Notice that in contrast to setNeedClientAuth(..) this
1131     * method will continue the negotiation if the client decide not to send
1132     * authentication credentials.
1133     *
1134     * @param want true if client authentication is
1135     *            desired, false if not.
1136     */
1137    @Override
1138    public void setWantClientAuth(boolean want) {
1139        sslParameters.setWantClientAuth(want);
1140    }
1141
1142    /**
1143     * This method is not supported for SSLSocket implementation.
1144     */
1145    @Override
1146    public void sendUrgentData(int data) throws IOException {
1147        throw new SocketException(
1148                "Method sendUrgentData() is not supported.");
1149    }
1150
1151    /**
1152     * This method is not supported for SSLSocket implementation.
1153     */
1154    @Override
1155    public void setOOBInline(boolean on) throws SocketException {
1156        throw new SocketException(
1157                "Methods sendUrgentData, setOOBInline are not supported.");
1158    }
1159
1160    /**
1161     * Set the read timeout on this socket. The SO_TIMEOUT option, is specified
1162     * in milliseconds. The read operation will block indefinitely for a zero
1163     * value.
1164     *
1165     * @param timeout the read timeout value
1166     * @throws SocketException if an error occurs setting the option
1167     */
1168    @Override
1169    public void setSoTimeout(int timeoutMilliseconds) throws SocketException {
1170        super.setSoTimeout(timeoutMilliseconds);
1171        this.timeoutMilliseconds = timeoutMilliseconds;
1172    }
1173
1174    @Override
1175    public int getSoTimeout() throws SocketException {
1176        return timeoutMilliseconds;
1177    }
1178
1179    /**
1180     * Set the handshake timeout on this socket.  This timeout is specified in
1181     * milliseconds and will be used only during the handshake process.
1182     *
1183     * @param timeout the handshake timeout value
1184     */
1185    public void setHandshakeTimeout(int timeoutMilliseconds) throws SocketException {
1186        this.handshakeTimeoutMilliseconds = timeoutMilliseconds;
1187    }
1188
1189    /**
1190     * Closes the SSL socket. Once closed, a socket is not available for further
1191     * use anymore under any circumstance. A new socket must be created.
1192     *
1193     * @throws <code>IOException</code> if an I/O error happens during the
1194     *             socket's closure.
1195     */
1196    @Override
1197    public void close() throws IOException {
1198        // TODO: Close SSL sockets using a background thread so they close
1199        // gracefully.
1200
1201        synchronized (handshakeLock) {
1202            if (!handshakeStarted) {
1203                // prevent further attemps to start handshake
1204                handshakeStarted = true;
1205
1206                synchronized (this) {
1207                    free();
1208
1209                    if (socket != this) {
1210                        if (autoClose && !socket.isClosed()) socket.close();
1211                    } else {
1212                        if (!super.isClosed()) super.close();
1213                    }
1214                }
1215
1216                return;
1217            }
1218        }
1219
1220        NativeCrypto.SSL_interrupt(sslNativePointer);
1221
1222        synchronized (this) {
1223            synchronized (writeLock) {
1224                synchronized (readLock) {
1225
1226                    // Shut down the SSL connection, per se.
1227                    try {
1228                        if (handshakeStarted) {
1229                            BlockGuard.getThreadPolicy().onNetwork();
1230                            NativeCrypto.SSL_shutdown(sslNativePointer, socket.getFileDescriptor$(),
1231                                    this);
1232                        }
1233                    } catch (IOException ignored) {
1234                        /*
1235                         * Note that although close() can throw
1236                         * IOException, the RI does not throw if there
1237                         * is problem sending a "close notify" which
1238                         * can happen if the underlying socket is closed.
1239                         */
1240                    }
1241
1242                    /*
1243                     * Even if the above call failed, it is still safe to free
1244                     * the native structs, and we need to do so lest we leak
1245                     * memory.
1246                     */
1247                    free();
1248
1249                    if (socket != this) {
1250                        if (autoClose && !socket.isClosed())
1251                            socket.close();
1252                    } else {
1253                        if (!super.isClosed())
1254                            super.close();
1255                    }
1256                }
1257            }
1258        }
1259    }
1260
1261    private void free() {
1262        if (sslNativePointer == 0) {
1263            return;
1264        }
1265        NativeCrypto.SSL_free(sslNativePointer);
1266        sslNativePointer = 0;
1267        guard.close();
1268    }
1269
1270    @Override protected void finalize() throws Throwable {
1271        try {
1272            /*
1273             * Just worry about our own state. Notably we do not try and
1274             * close anything. The SocketImpl, either our own
1275             * PlainSocketImpl, or the Socket we are wrapping, will do
1276             * that. This might mean we do not properly SSL_shutdown, but
1277             * if you want to do that, properly close the socket yourself.
1278             *
1279             * The reason why we don't try to SSL_shutdown, is that there
1280             * can be a race between finalizers where the PlainSocketImpl
1281             * finalizer runs first and closes the socket. However, in the
1282             * meanwhile, the underlying file descriptor could be reused
1283             * for another purpose. If we call SSL_shutdown, the
1284             * underlying socket BIOs still have the old file descriptor
1285             * and will write the close notify to some unsuspecting
1286             * reader.
1287             */
1288            if (guard != null) {
1289                guard.warnIfOpen();
1290            }
1291            free();
1292        } finally {
1293            super.finalize();
1294        }
1295    }
1296}
1297